Skip to main content

driver_debug_lib/
lib.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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
13/// Connects to the `fuchsia.driver.debug.Debug` protocol at the given path or default paths.
14pub 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
31/// Formats a list of `CommandInfo` into a human-readable table.
32pub 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
44/// Queries the debug proxy for supported commands and returns the formatted table.
45pub 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
54/// Executes a debug command with the given arguments over the FIDL proxy.
55pub 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}