1use std::sync::mpsc::{Receiver, RecvTimeoutError, Sender};
10use std::time::{Duration, Instant};
11
12use rust_socketio::{ClientBuilder, Payload};
13use serde_json::{json, Value};
14
15use crate::error::{Error, Result};
16
17const CONNECT_TIMEOUT: Duration = Duration::from_secs(15);
19
20#[derive(Debug)]
21enum UartEvent {
22 Connected { device_path: String, baudrate: u64 },
23 Data(Vec<u8>),
24 Status(String),
25 Error(String),
26 Stopped,
27}
28
29fn hex_encode(data: &[u8]) -> String {
30 let mut s = String::with_capacity(data.len() * 2);
31 for b in data {
32 s.push_str(&format!("{b:02x}"));
33 }
34 s
35}
36
37fn hex_decode(s: &str) -> Option<Vec<u8>> {
38 let s = s.trim();
39 if s.len() % 2 != 0 {
40 return None;
41 }
42 (0..s.len())
43 .step_by(2)
44 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).ok())
45 .collect()
46}
47
48fn payload_json(payload: Payload) -> Option<Value> {
50 match payload {
51 Payload::Text(values) => values.into_iter().next(),
52 #[allow(deprecated)]
53 Payload::String(s) => serde_json::from_str(&s).ok(),
54 Payload::Binary(_) => None,
55 }
56}
57
58pub struct Uart {
65 socket: Option<rust_socketio::client::Client>,
66 rx: Receiver<UartEvent>,
67 netname: String,
68 device_path: String,
69 baudrate: u64,
70 buf: Vec<u8>,
71 last_status: Option<String>,
72}
73
74impl Uart {
75 pub(crate) fn open(
76 base_url: &str,
77 netname: String,
78 bearer_token: Option<String>,
79 ) -> Result<Self> {
80 let (tx, rx) = std::sync::mpsc::channel::<UartEvent>();
81
82 let socket = {
83 let tx_connected: Sender<UartEvent> = tx.clone();
84 let tx_data = tx.clone();
85 let tx_status = tx.clone();
86 let tx_error = tx.clone();
87 let tx_stopped = tx;
88 let mut builder = ClientBuilder::new(base_url).namespace("/uart");
89 if let Some(token) = &bearer_token {
92 builder = builder.opening_header("Authorization", format!("Bearer {token}"));
93 }
94 builder
95 .on("uart_connected", move |payload, _| {
96 let info = payload_json(payload).unwrap_or(Value::Null);
97 let device_path = info
98 .get("device_path")
99 .and_then(Value::as_str)
100 .unwrap_or_default()
101 .to_string();
102 let baudrate = info.get("baudrate").and_then(Value::as_u64).unwrap_or(0);
103 let _ = tx_connected.send(UartEvent::Connected {
104 device_path,
105 baudrate,
106 });
107 })
108 .on("uart_data", move |payload, _| {
109 if let Some(bytes) = payload_json(payload)
110 .as_ref()
111 .and_then(|v| v.get("data"))
112 .and_then(Value::as_str)
113 .and_then(hex_decode)
114 {
115 let _ = tx_data.send(UartEvent::Data(bytes));
116 }
117 })
118 .on("uart_status", move |payload, _| {
119 if let Some(status) = payload_json(payload)
122 .as_ref()
123 .and_then(|v| v.get("status"))
124 .and_then(Value::as_str)
125 {
126 let _ = tx_status.send(UartEvent::Status(status.to_string()));
127 }
128 })
129 .on("error", move |payload, _| {
130 let message = payload_json(payload)
131 .as_ref()
132 .and_then(|v| v.get("message"))
133 .and_then(Value::as_str)
134 .unwrap_or("unknown UART error")
135 .to_string();
136 let _ = tx_error.send(UartEvent::Error(message));
137 })
138 .on("uart_stopped", move |_, _| {
139 let _ = tx_stopped.send(UartEvent::Stopped);
140 })
141 .connect()
142 .map_err(|e| Error::Connection(format!("Socket.IO connect failed: {e}")))?
143 };
144
145 socket
146 .emit("start_uart", json!({ "netname": netname, "overrides": {} }))
147 .map_err(|e| Error::Stream(format!("could not start UART session: {e}")))?;
148
149 let deadline = Instant::now() + CONNECT_TIMEOUT;
151 let mut uart = Uart {
152 socket: Some(socket),
153 rx,
154 netname,
155 device_path: String::new(),
156 baudrate: 0,
157 buf: Vec::new(),
158 last_status: None,
159 };
160 loop {
161 let remaining = deadline.saturating_duration_since(Instant::now());
162 if remaining.is_zero() {
163 return Err(Error::Timeout(format!(
164 "box did not confirm UART session for '{}' within {CONNECT_TIMEOUT:?}",
165 uart.netname
166 )));
167 }
168 match uart.rx.recv_timeout(remaining) {
169 Ok(UartEvent::Connected {
170 device_path,
171 baudrate,
172 }) => {
173 uart.device_path = device_path;
174 uart.baudrate = baudrate;
175 return Ok(uart);
176 }
177 Ok(UartEvent::Data(bytes)) => uart.buf.extend_from_slice(&bytes),
179 Ok(UartEvent::Error(msg)) => return Err(Error::Stream(msg)),
180 Ok(_) => {}
181 Err(RecvTimeoutError::Timeout) => {}
182 Err(RecvTimeoutError::Disconnected) => {
183 return Err(Error::Stream("UART session closed unexpectedly".to_string()))
184 }
185 }
186 }
187 }
188
189 pub fn netname(&self) -> &str {
191 &self.netname
192 }
193
194 pub fn device_path(&self) -> &str {
196 &self.device_path
197 }
198
199 pub fn baudrate(&self) -> u64 {
201 self.baudrate
202 }
203
204 pub fn last_status(&self) -> Option<&str> {
208 self.last_status.as_deref()
209 }
210
211 pub fn write(&self, data: &[u8]) -> Result<()> {
213 let socket = self
214 .socket
215 .as_ref()
216 .ok_or_else(|| Error::Stream("UART session already stopped".to_string()))?;
217 socket
218 .emit("uart_write", json!({ "data": hex_encode(data) }))
219 .map_err(|e| Error::Stream(format!("UART write failed: {e}")))
220 }
221
222 pub fn write_str(&self, s: &str) -> Result<()> {
224 self.write(s.as_bytes())
225 }
226
227 fn pump(&mut self, wait: Duration) -> Result<()> {
230 let mut wait = wait;
231 loop {
232 match self.rx.recv_timeout(wait) {
233 Ok(UartEvent::Data(bytes)) => {
234 self.buf.extend_from_slice(&bytes);
235 wait = Duration::ZERO;
238 }
239 Ok(UartEvent::Error(msg)) => return Err(Error::Stream(msg)),
240 Ok(UartEvent::Status(status)) => self.last_status = Some(status),
241 Ok(_) => {}
242 Err(RecvTimeoutError::Timeout) => return Ok(()),
243 Err(RecvTimeoutError::Disconnected) => {
244 return Err(Error::Stream("UART session closed unexpectedly".to_string()))
245 }
246 }
247 }
248 }
249
250 pub fn read(&mut self, timeout: Duration) -> Result<Vec<u8>> {
253 self.pump(timeout)?;
254 Ok(std::mem::take(&mut self.buf))
255 }
256
257 pub fn wait_for(&mut self, needle: &[u8], timeout: Duration) -> Result<Vec<u8>> {
262 if needle.is_empty() {
263 return Ok(Vec::new());
264 }
265 let deadline = Instant::now() + timeout;
266 loop {
267 if let Some(pos) = self
268 .buf
269 .windows(needle.len())
270 .position(|w| w == needle)
271 {
272 let mut rest = self.buf.split_off(pos + needle.len());
273 std::mem::swap(&mut self.buf, &mut rest);
274 return Ok(rest);
275 }
276 let remaining = deadline.saturating_duration_since(Instant::now());
277 if remaining.is_zero() {
278 return Err(Error::Timeout(format!(
279 "'{}' did not appear on UART '{}' within {timeout:?}",
280 String::from_utf8_lossy(needle),
281 self.netname
282 )));
283 }
284 self.pump(remaining)?;
285 }
286 }
287
288 pub fn stop(mut self) -> Result<()> {
290 self.shutdown();
291 Ok(())
292 }
293
294 fn shutdown(&mut self) {
295 if let Some(socket) = self.socket.take() {
296 let _ = socket.emit("stop_uart", json!({}));
297 let _ = socket.disconnect();
298 }
299 }
300}
301
302impl Drop for Uart {
303 fn drop(&mut self) {
304 self.shutdown();
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::{hex_decode, hex_encode};
311
312 #[test]
313 fn hex_roundtrip() {
314 let data = [0x00, 0x0a, 0xff, 0x42];
315 assert_eq!(hex_encode(&data), "000aff42");
316 assert_eq!(hex_decode(&hex_encode(&data)).unwrap(), data);
317 assert!(hex_decode("zz").is_none());
318 assert!(hex_decode("abc").is_none());
319 }
320}