1use std::time::Duration;
4
5use serde_json::Value;
6
7use crate::auth::{self, GatewayAuth};
8use crate::error::{Error, Result};
9use crate::nets::adc::Adc;
10use crate::nets::arm::Arm;
11use crate::nets::battery::Battery;
12use crate::nets::ble::Ble;
13use crate::nets::blufi::Blufi;
14use crate::nets::dac::Dac;
15use crate::nets::debug::DebugNet;
16use crate::nets::eload::Eload;
17use crate::nets::energy::EnergyAnalyzer;
18use crate::nets::gpio::Gpio;
19use crate::nets::i2c::I2c;
20use crate::nets::router::Router;
21use crate::nets::scope::Scope;
22use crate::nets::solar::Solar;
23use crate::nets::spi::Spi;
24use crate::nets::supply::Supply;
25use crate::nets::thermocouple::Thermocouple;
26use crate::nets::usb::UsbPort;
27use crate::nets::watt::WattMeter;
28use crate::nets::webcam::Webcam;
29use crate::nets::wifi::Wifi;
30use crate::wire::{self, BoxStatus, Health, HttpRequest, Method, NetRecord, Op, Timeout};
31use crate::BOX_HOST_ENV;
32
33pub struct LagerBox {
53 base: String,
54 debug_base: String,
55 agent: ureq::Agent,
56 default_timeout: Duration,
57 auth: GatewayAuth,
58}
59
60pub struct LagerBoxBuilder {
63 host: String,
64 debug_url: Option<String>,
65 default_timeout: Duration,
66 bearer_token: Option<String>,
67}
68
69impl LagerBoxBuilder {
70 pub fn timeout(mut self, timeout: Duration) -> Self {
74 self.default_timeout = timeout;
75 self
76 }
77
78 pub fn debug_service_url(mut self, url: impl Into<String>) -> Self {
83 self.debug_url = Some(url.into());
84 self
85 }
86
87 pub fn bearer_token(mut self, token: impl Into<String>) -> Self {
97 self.bearer_token = Some(token.into());
98 self
99 }
100
101 pub fn build(self) -> Result<LagerBox> {
103 let base = wire::base_url(&self.host)?;
104 let debug_base = match self.debug_url {
105 Some(url) => wire::base_url_with_port(&url, wire::DEBUG_SERVICE_PORT)?,
106 None => wire::service_base(&base, wire::DEBUG_SERVICE_PORT),
107 };
108 let auth = GatewayAuth::new(&base, self.bearer_token);
109 Ok(LagerBox {
110 base,
111 debug_base,
112 agent: ureq::AgentBuilder::new().build(),
113 default_timeout: self.default_timeout,
114 auth,
115 })
116 }
117}
118
119impl LagerBox {
120 pub fn connect(host: impl Into<String>) -> Result<Self> {
123 Self::builder(host).build()
124 }
125
126 pub fn from_env() -> Result<Self> {
129 let host = std::env::var(BOX_HOST_ENV)
130 .map_err(|_| Error::Config(format!("{BOX_HOST_ENV} is not set")))?;
131 Self::connect(host)
132 }
133
134 pub fn builder(host: impl Into<String>) -> LagerBoxBuilder {
136 LagerBoxBuilder {
137 host: host.into(),
138 debug_url: std::env::var(crate::DEBUG_SERVICE_URL_ENV).ok(),
139 default_timeout: wire::DEFAULT_TIMEOUT,
140 bearer_token: None,
141 }
142 }
143
144 pub fn base_url(&self) -> &str {
146 &self.base
147 }
148
149 pub(crate) fn execute(&self, req: &HttpRequest) -> Result<(u16, Value)> {
153 self.execute_at(&self.base, req)
154 }
155
156 pub(crate) fn execute_debug(&self, req: &HttpRequest) -> Result<(u16, Value)> {
158 self.execute_at(&self.debug_base, req)
159 }
160
161 fn execute_at(&self, base: &str, req: &HttpRequest) -> Result<(u16, Value)> {
166 let token = self.current_token();
167 let (status, gateway, resp_body) = self.send_once(base, req, token.as_deref())?;
168
169 let Some(auth_url) = gateway else {
170 return Ok((status, resp_body));
171 };
172 self.auth.learn_auth_server(&auth_url);
175 if status == 401 && !self.auth.has_static_token() {
176 if let Some(fresh) = self
177 .auth
178 .resolve_token_blocking(&auth_url, token.as_deref())
179 {
180 let (status, gateway, resp_body) =
181 self.send_once(base, req, Some(&fresh))?;
182 let Some(auth_url) = gateway else {
183 return Ok((status, resp_body));
184 };
185 return Err(auth::denial_error(
186 status,
187 self.auth.box_host(),
188 &auth_url,
189 true,
190 ));
191 }
192 }
193 Err(auth::denial_error(
194 status,
195 self.auth.box_host(),
196 &auth_url,
197 token.is_some(),
198 ))
199 }
200
201 fn current_token(&self) -> Option<String> {
204 if let Some(token) = self.auth.cached_token() {
205 return Some(token);
206 }
207 if self.auth.wants_store_token() {
208 let auth_url = self.auth.auth_url()?;
209 return self.auth.resolve_token_blocking(&auth_url, None);
210 }
211 None
212 }
213
214 fn send_once(
218 &self,
219 base: &str,
220 req: &HttpRequest,
221 token: Option<&str>,
222 ) -> Result<(u16, Option<String>, Value)> {
223 let url = format!("{}{}", base, req.path);
224 let mut r = match req.method {
225 Method::Get => self.agent.request("GET", &url),
226 Method::Post => self.agent.request("POST", &url),
227 };
228 match req.timeout {
229 Timeout::Default => r = r.timeout(self.default_timeout),
230 Timeout::After(d) => r = r.timeout(d),
231 Timeout::Unbounded => {}
232 }
233 if let Some(token) = token {
234 r = r.set("Authorization", &format!("Bearer {token}"));
235 }
236 let outcome = match (&req.method, &req.body) {
237 (Method::Post, Some(body)) => r.send_json(body.clone()),
238 _ => r.call(),
239 };
240 let resp = match outcome {
241 Ok(resp) => resp,
242 Err(ureq::Error::Status(_, resp)) => resp,
245 Err(ureq::Error::Transport(t)) => {
246 let msg = t.to_string();
247 return if msg.to_ascii_lowercase().contains("timed out")
248 || msg.to_ascii_lowercase().contains("timeout")
249 {
250 Err(Error::Timeout(msg))
251 } else {
252 Err(Error::Connection(msg))
253 };
254 }
255 };
256 let status = resp.status();
257 let gateway = if auth::is_denial(status) {
258 resp.header(auth::DISCOVERY_HEADER).map(str::to_string)
259 } else {
260 None
261 };
262 let body: Value = match resp.into_json() {
263 Ok(body) => body,
264 Err(e) if status < 400 => {
265 return Err(Error::Decode(format!("non-JSON response: {e}")))
266 }
267 Err(_) => Value::Null,
269 };
270 if gateway.is_none() && status >= 400 && body.is_null() {
271 return Err(Error::Box {
272 status,
273 message: format!("HTTP {status} (non-JSON body)"),
274 });
275 }
276 Ok((status, gateway, body))
277 }
278
279 pub(crate) fn run<T>(&self, op: Op<T>) -> Result<T> {
281 let (status, body) = self.execute(&op.req)?;
282 let resp = wire::parse_command(status, body)?;
283 (op.parse)(resp)
284 }
285
286 pub(crate) fn stream_debug(
289 &self,
290 req: &HttpRequest,
291 ) -> Result<Box<dyn std::io::Read + Send + Sync>> {
292 let token = self.current_token();
293 match self.stream_debug_once(req, token.as_deref()) {
294 Err(Error::AuthRequired {
298 box_host,
299 auth_url,
300 message,
301 }) if !self.auth.has_static_token() => {
302 match self
303 .auth
304 .resolve_token_blocking(&auth_url, token.as_deref())
305 {
306 Some(fresh) => self.stream_debug_once(req, Some(&fresh)),
307 None => Err(Error::AuthRequired {
308 box_host,
309 auth_url,
310 message,
311 }),
312 }
313 }
314 other => other,
315 }
316 }
317
318 fn stream_debug_once(
319 &self,
320 req: &HttpRequest,
321 token: Option<&str>,
322 ) -> Result<Box<dyn std::io::Read + Send + Sync>> {
323 let url = format!("{}{}", self.debug_base, req.path);
324 let mut r = self.agent.request("POST", &url);
325 match req.timeout {
326 Timeout::Default => r = r.timeout(self.default_timeout),
327 Timeout::After(d) => r = r.timeout(d),
328 Timeout::Unbounded => {}
329 }
330 if let Some(token) = token {
331 r = r.set("Authorization", &format!("Bearer {token}"));
332 }
333 let outcome = match &req.body {
334 Some(body) => r.send_json(body.clone()),
335 None => r.call(),
336 };
337 match outcome {
338 Ok(resp) => Ok(resp.into_reader()),
339 Err(ureq::Error::Status(status, resp)) => {
340 if auth::is_denial(status) {
341 if let Some(auth_url) = resp.header(auth::DISCOVERY_HEADER) {
342 let auth_url = auth_url.to_string();
343 self.auth.learn_auth_server(&auth_url);
344 return Err(auth::denial_error(
345 status,
346 self.auth.box_host(),
347 &auth_url,
348 token.is_some(),
349 ));
350 }
351 }
352 let body: Value = resp.into_json().unwrap_or(Value::Null);
353 Err(wire::parse_debug(status, body).unwrap_err())
354 }
355 Err(ureq::Error::Transport(t)) => Err(Error::Connection(t.to_string())),
356 }
357 }
358
359 pub(crate) fn debug_net_record(&self, name: &str) -> Result<Value> {
362 let records = self.nets_raw()?;
363 crate::nets::debug::find_debug_record(records, name)
364 }
365
366 fn nets_raw(&self) -> Result<Vec<Value>> {
369 let body = match self.get_json("/nets/list") {
370 Ok(body) => body,
371 Err(primary) => self.get_json("/uart/nets/list").map_err(|_| primary)?,
372 };
373 Ok(wire::nets_list_values(body))
374 }
375
376 fn get_json(&self, path: &str) -> Result<Value> {
377 let (status, body) = self.execute(&wire::get(path))?;
378 if status != 200 {
379 return Err(Error::Box {
380 status,
381 message: body
382 .get("error")
383 .and_then(Value::as_str)
384 .unwrap_or("request failed")
385 .to_string(),
386 });
387 }
388 Ok(body)
389 }
390
391 pub fn nets(&self) -> Result<Vec<NetRecord>> {
398 match self.get_json("/nets/list") {
399 Ok(body) => wire::nets_from_body(body),
400 Err(primary) => match self.get_json("/uart/nets/list") {
401 Ok(body) => wire::nets_from_body(body),
402 Err(_) => Err(primary),
403 },
404 }
405 }
406
407 pub fn health(&self) -> Result<Health> {
409 let body = self.get_json("/health")?;
410 serde_json::from_value(body).map_err(Into::into)
411 }
412
413 pub fn status(&self) -> Result<BoxStatus> {
415 let body = self.get_json("/status")?;
416 serde_json::from_value(body).map_err(Into::into)
417 }
418
419 pub fn supply(&self, name: impl Into<String>) -> Supply<'_> {
423 Supply { client: self, name: name.into() }
424 }
425
426 pub fn battery(&self, name: impl Into<String>) -> Battery<'_> {
428 Battery { client: self, name: name.into() }
429 }
430
431 pub fn eload(&self, name: impl Into<String>) -> Eload<'_> {
433 Eload { client: self, name: name.into() }
434 }
435
436 pub fn solar(&self, name: impl Into<String>) -> Solar<'_> {
438 Solar { client: self, name: name.into() }
439 }
440
441 pub fn gpio(&self, name: impl Into<String>) -> Gpio<'_> {
443 Gpio { client: self, name: name.into() }
444 }
445
446 pub fn adc(&self, name: impl Into<String>) -> Adc<'_> {
448 Adc { client: self, name: name.into() }
449 }
450
451 pub fn dac(&self, name: impl Into<String>) -> Dac<'_> {
453 Dac { client: self, name: name.into() }
454 }
455
456 pub fn thermocouple(&self, name: impl Into<String>) -> Thermocouple<'_> {
458 Thermocouple { client: self, name: name.into() }
459 }
460
461 pub fn watt_meter(&self, name: impl Into<String>) -> WattMeter<'_> {
463 WattMeter { client: self, name: name.into() }
464 }
465
466 pub fn energy_analyzer(&self, name: impl Into<String>) -> EnergyAnalyzer<'_> {
468 EnergyAnalyzer { client: self, name: name.into() }
469 }
470
471 pub fn spi(&self, name: impl Into<String>) -> Spi<'_> {
473 Spi { client: self, name: name.into() }
474 }
475
476 pub fn i2c(&self, name: impl Into<String>) -> I2c<'_> {
478 I2c { client: self, name: name.into() }
479 }
480
481 pub fn usb(&self, name: impl Into<String>) -> UsbPort<'_> {
483 UsbPort { client: self, name: name.into() }
484 }
485
486 pub fn arm(&self, name: impl Into<String>) -> Arm<'_> {
488 Arm { client: self, name: name.into() }
489 }
490
491 pub fn webcam(&self, name: impl Into<String>) -> Webcam<'_> {
493 Webcam { client: self, name: name.into() }
494 }
495
496 pub fn router(&self, name: impl Into<String>) -> Router<'_> {
498 Router { client: self, name: name.into() }
499 }
500
501 pub fn ble(&self) -> Ble<'_> {
503 Ble { client: self }
504 }
505
506 pub fn wifi(&self) -> Wifi<'_> {
508 Wifi { client: self }
509 }
510
511 pub fn blufi(&self) -> Blufi<'_> {
513 Blufi { client: self }
514 }
515
516 pub fn debug(&self, name: impl Into<String>) -> DebugNet<'_> {
519 DebugNet { client: self, name: name.into() }
520 }
521
522 pub fn scope(&self, name: impl Into<String>) -> Scope {
524 Scope::new(name)
525 }
526
527 #[cfg(feature = "uart")]
532 pub fn uart(&self, name: impl Into<String>) -> Result<crate::nets::uart::Uart> {
533 crate::nets::uart::Uart::open(&self.base, name.into(), self.current_token())
534 }
535}