1use anyhow::{Context, Result, anyhow};
6use fidl_fuchsia_driver_debug as fdebug;
7use fuchsia_component::client::connect_to_protocol_at_path;
8use std::path::Path;
9
10pub const DEFAULT_OUT_SVC_PATH: &str = "/out/svc/fuchsia.driver.debug.Debug";
11pub const DEFAULT_SVC_PATH: &str = "/svc/fuchsia.driver.debug.Debug";
12
13pub fn connect_to_debug_protocol(custom_path: Option<&str>) -> Result<fdebug::DebugProxy> {
15 if let Some(path) = custom_path {
16 return connect_to_protocol_at_path::<fdebug::DebugMarker>(path)
17 .with_context(|| format!("Failed to connect to Debug protocol at {path}"));
18 }
19
20 if Path::new(DEFAULT_OUT_SVC_PATH).exists() {
21 if let Ok(proxy) = connect_to_protocol_at_path::<fdebug::DebugMarker>(DEFAULT_OUT_SVC_PATH)
22 {
23 return Ok(proxy);
24 }
25 }
26
27 connect_to_protocol_at_path::<fdebug::DebugMarker>(DEFAULT_SVC_PATH)
28 .with_context(|| format!("Failed to connect to Debug protocol at {DEFAULT_SVC_PATH}"))
29}
30
31pub fn format_command_info_table(commands: &[fdebug::CommandInfo]) -> String {
33 let mut out = String::new();
34 out.push_str(&format!("{:<20} {}\n", "COMMAND", "DESCRIPTION"));
35 out.push_str(&format!("{:<20} {}\n", "-------", "-----------"));
36 for cmd in commands {
37 let name = cmd.name.as_deref().unwrap_or("<unknown>");
38 let desc = cmd.description.as_deref().unwrap_or("");
39 out.push_str(&format!("{:<20} {}\n", name, desc));
40 }
41 out
42}
43
44pub async fn list_commands(proxy: &fdebug::DebugProxy) -> Result<String> {
46 let commands = proxy
47 .list_commands()
48 .await
49 .context("FIDL error calling ListCommands")?
50 .map_err(|s| anyhow!("ListCommands error: {}", zx::Status::err_from_raw(s)))?;
51 Ok(format_command_info_table(&commands))
52}
53
54pub async fn execute_command(
56 proxy: &fdebug::DebugProxy,
57 args: &[String],
58 stdout: zx::Socket,
59 stderr: zx::Socket,
60) -> Result<i32> {
61 proxy
62 .execute(args, stdout, stderr)
63 .await
64 .context("FIDL error calling Execute")?
65 .map_err(|s| anyhow!("Execute error: {}", zx::Status::err_from_raw(s)))
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 #[test]
73 fn test_format_command_info_table() {
74 let commands = vec![
75 fdebug::CommandInfo {
76 name: Some("ping".to_string()),
77 description: Some("Ping driver".to_string()),
78 ..Default::default()
79 },
80 fdebug::CommandInfo {
81 name: Some("reset".to_string()),
82 description: Some("Reset device".to_string()),
83 ..Default::default()
84 },
85 ];
86 let table = format_command_info_table(&commands);
87 assert!(table.contains("COMMAND"));
88 assert!(table.contains("DESCRIPTION"));
89 assert!(table.contains("ping"));
90 assert!(table.contains("Ping driver"));
91 assert!(table.contains("reset"));
92 assert!(table.contains("Reset device"));
93 }
94
95 #[fuchsia_async::run_singlethreaded(test)]
96 async fn test_execute_command() {
97 use futures::StreamExt;
98 use futures::io::AsyncReadExt;
99
100 let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<fdebug::DebugMarker>();
101
102 let (local_stdout, remote_stdout) = zx::Socket::create_stream();
103 let (local_stderr, remote_stderr) = zx::Socket::create_stream();
104
105 let mut async_stdout = fuchsia_async::Socket::from_socket(local_stdout);
106 let mut async_stderr = fuchsia_async::Socket::from_socket(local_stderr);
107
108 let server = async move {
109 if let Some(request) = stream.next().await {
110 match request.expect("stream request") {
111 fdebug::DebugRequest::Execute { args, stdout, stderr, responder } => {
112 assert_eq!(args, vec!["echo".to_string(), "hello".to_string()]);
113 let _ = stdout.write(b"hello world\n").expect("write to stdout");
114 let _ = stderr.write(b"no errors\n").expect("write to stderr");
115 drop(stdout);
116 drop(stderr);
117 responder.send(Ok(0)).expect("send response");
118 }
119 _ => panic!("unexpected request"),
120 }
121 }
122 };
123
124 let mut stdout_bytes = Vec::new();
125 let mut stderr_bytes = Vec::new();
126
127 let stdout_reader = async {
128 async_stdout.read_to_end(&mut stdout_bytes).await.unwrap();
129 };
130 let stderr_reader = async {
131 async_stderr.read_to_end(&mut stderr_bytes).await.unwrap();
132 };
133
134 let client = async move {
135 execute_command(
136 &proxy,
137 &["echo".to_string(), "hello".to_string()],
138 remote_stdout,
139 remote_stderr,
140 )
141 .await
142 .expect("execute command")
143 };
144
145 let (_, _, _, exit_code) = futures::join!(server, stdout_reader, stderr_reader, client);
146 assert_eq!(exit_code, 0);
147 assert_eq!(String::from_utf8_lossy(&stdout_bytes), "hello world\n");
148 assert_eq!(String::from_utf8_lossy(&stderr_bytes), "no errors\n");
149 }
150}