opentitanlib/uart/
console.rs1use anyhow::{Result, anyhow};
6use regex::{Captures, Regex};
7use std::fs::File;
8use std::io::Write;
9use std::os::fd::AsFd;
10use std::time::{Duration, Instant, SystemTime};
11
12use tokio::io::AsyncReadExt;
13
14use crate::io::console::{ConsoleDevice, ConsoleError};
15
16#[derive(Default)]
17pub struct UartConsole {
18 pub logfile: Option<File>,
19 pub timeout: Option<Duration>,
20 pub deadline: Option<Instant>,
21 pub exit_success: Option<Regex>,
22 pub exit_failure: Option<Regex>,
23 pub timestamp: bool,
24 pub buffer: String,
25 pub newline: bool,
26 pub break_en: bool,
27}
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub enum ExitStatus {
31 None,
32 CtrlC,
33 Timeout,
34 ExitSuccess,
35 ExitFailure,
36}
37
38pub trait ReadAsFd: tokio::io::AsyncRead + AsFd + std::marker::Unpin {}
40impl<T: tokio::io::AsyncRead + AsFd + std::marker::Unpin> ReadAsFd for T {}
41
42impl UartConsole {
43 const CTRL_B: u8 = 2;
44 const CTRL_C: u8 = 3;
45 const BUFFER_LEN: usize = 32768;
46
47 pub fn interact<T>(
49 &mut self,
50 device: &T,
51 stdin: Option<&mut dyn ReadAsFd>,
52 stdout: Option<&mut dyn Write>,
53 ) -> Result<ExitStatus>
54 where
55 T: ConsoleDevice + ?Sized,
56 {
57 if let Some(timeout) = &self.timeout {
58 self.deadline = Some(Instant::now() + *timeout);
59 }
60 crate::util::runtime::block_on(self.interact_async(device, stdin, stdout))
61 }
62
63 async fn interact_async<T>(
66 &mut self,
67 device: &T,
68 mut stdin: Option<&mut dyn ReadAsFd>,
69 mut stdout: Option<&mut dyn Write>,
70 ) -> Result<ExitStatus>
71 where
72 T: ConsoleDevice + ?Sized,
73 {
74 let mut break_en = self.break_en;
75 let deadline = self.deadline;
76 let tx = async {
77 if let Some(stdin) = stdin.as_mut() {
78 Self::process_input(&mut break_en, device, stdin).await
79 } else {
80 std::future::pending().await
81 }
82 };
83 let rx = async {
84 loop {
85 self.uart_read(device, &mut stdout).await?;
86 if self
87 .exit_success
88 .as_ref()
89 .map(|rx| rx.is_match(&self.buffer))
90 == Some(true)
91 {
92 return Ok(ExitStatus::ExitSuccess);
93 }
94 if self
95 .exit_failure
96 .as_ref()
97 .map(|rx| rx.is_match(&self.buffer))
98 == Some(true)
99 {
100 return Ok(ExitStatus::ExitFailure);
101 }
102 }
103 };
104 let deadline = async {
105 if let Some(deadline) = deadline {
106 tokio::time::sleep_until(tokio::time::Instant::from_std(deadline)).await;
107 } else {
108 std::future::pending().await
109 }
110 };
111
112 let r = tokio::select! {
113 v = tx => v,
114 v = rx => v,
115 _ = deadline => Ok(ExitStatus::Timeout),
116 };
117 self.break_en = break_en;
118 r
119 }
120
121 fn uses_regex(&self) -> bool {
126 self.exit_success.is_some() || self.exit_failure.is_some()
127 }
128
129 fn append_buffer(&mut self, data: &[u8]) {
131 self.buffer.push_str(&String::from_utf8_lossy(data));
132 while self.buffer.len() > UartConsole::BUFFER_LEN {
133 self.buffer.remove(0);
134 }
135 }
136
137 async fn uart_read<T>(&mut self, device: &T, stdout: &mut Option<&mut dyn Write>) -> Result<()>
139 where
140 T: ConsoleDevice + ?Sized,
141 {
142 let mut buf = [0u8; 1024];
143 let effective_buf = if self.uses_regex() {
144 &mut buf[..1]
147 } else {
148 &mut buf
149 };
150 let len = std::future::poll_fn(|cx| device.console_poll_read(cx, effective_buf)).await?;
151 for i in 0..len {
152 if self.timestamp && self.newline {
153 let t = humantime::format_rfc3339_millis(SystemTime::now());
154 stdout.as_mut().map_or(Ok(()), |out| {
155 out.write_fmt(format_args!("[{} console]", t))
156 })?;
157 self.newline = false;
158 }
159 self.newline = buf[i] == b'\n';
160 stdout
161 .as_mut()
162 .map_or(Ok(()), |out| out.write_all(&buf[i..i + 1]))?;
163 }
164 stdout.as_mut().map_or(Ok(()), |out| out.flush())?;
165
166 self.logfile
168 .as_mut()
169 .map_or(Ok(()), |f| f.write_all(&buf[..len]))?;
170 if self.uses_regex() {
171 self.append_buffer(&buf[..len]);
172 }
173 Ok(())
174 }
175
176 async fn process_input<T>(
177 break_en: &mut bool,
178 device: &T,
179 stdin: &mut dyn ReadAsFd,
180 ) -> Result<ExitStatus>
181 where
182 T: ConsoleDevice + ?Sized,
183 {
184 loop {
185 let mut buf = [0u8; 256];
186 let len = stdin.read(&mut buf).await?;
187 if len == 1 {
188 if buf[0] == UartConsole::CTRL_C {
189 return Ok(ExitStatus::CtrlC);
190 }
191 if buf[0] == UartConsole::CTRL_B {
192 *break_en = !*break_en;
193 eprint!(
194 "\r\n{} break",
195 if *break_en { "Setting" } else { "Clearing" }
196 );
197 let b = device.set_break(*break_en);
198 if b.is_err() {
199 eprint!(": {:?}", b);
200 }
201 eprint!("\r\n");
202 continue;
203 }
204 }
205 if len > 0 {
206 device.console_write(&buf[..len])?;
207 }
208 }
209 }
210
211 pub fn captures(&self, status: ExitStatus) -> Option<Captures<'_>> {
212 match status {
213 ExitStatus::ExitSuccess => self
214 .exit_success
215 .as_ref()
216 .and_then(|rx| rx.captures(&self.buffer)),
217 ExitStatus::ExitFailure => self
218 .exit_failure
219 .as_ref()
220 .and_then(|rx| rx.captures(&self.buffer)),
221 _ => None,
222 }
223 }
224
225 pub fn wait_for<T>(device: &T, rx: &str, timeout: Duration) -> Result<Vec<String>>
226 where
227 T: ConsoleDevice + ?Sized,
228 {
229 let mut console = UartConsole {
230 timestamp: true,
231 newline: true,
232 timeout: Some(timeout),
233 exit_success: Some(Regex::new(rx)?),
234 ..Default::default()
235 };
236 let mut stdout = std::io::stdout();
237 let result = console.interact(device, None, Some(&mut stdout))?;
238 println!();
239 match result {
240 ExitStatus::ExitSuccess => {
241 let caps = console.captures(ExitStatus::ExitSuccess).expect("capture");
242 let mut vec = Vec::new();
243 for c in caps.iter() {
244 match c {
245 None => vec.push(String::new()),
246 _ => vec.push(c.unwrap().as_str().to_owned()),
247 }
248 }
249 Ok(vec)
250 }
251 ExitStatus::Timeout => Err(ConsoleError::GenericError("Timed Out".into()).into()),
252 _ => Err(anyhow!("Impossible result: {:?}", result)),
253 }
254 }
255}