1use crate::common::config::TcpSpawnStyle;
6use crate::common::{parse_listen_address, Error, Result};
7use std::path::Path;
8use std::process::Stdio;
9use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
10use tokio::net::TcpStream;
11use tokio::process::{Child, Command};
12use tokio::time::{timeout, Duration};
13
14#[derive(Debug, Clone)]
16pub struct VerifyResult {
17 pub success: bool,
19 pub capabilities: Option<DapCapabilities>,
21 pub error: Option<String>,
23}
24
25#[derive(Debug, Clone, Default)]
27pub struct DapCapabilities {
28 pub supports_configuration_done_request: bool,
29 pub supports_function_breakpoints: bool,
30 pub supports_conditional_breakpoints: bool,
31 pub supports_evaluate_for_hovers: bool,
32}
33
34pub async fn verify_dap_adapter(
36 path: &Path,
37 args: &[String],
38) -> Result<VerifyResult> {
39 let mut child = spawn_adapter(path, args).await?;
41
42 let init_result = timeout(Duration::from_secs(5), send_initialize(&mut child)).await;
44
45 let _ = child.kill().await;
47
48 match init_result {
49 Ok(Ok(caps)) => Ok(VerifyResult {
50 success: true,
51 capabilities: Some(caps),
52 error: None,
53 }),
54 Ok(Err(e)) => Ok(VerifyResult {
55 success: false,
56 capabilities: None,
57 error: Some(e.to_string()),
58 }),
59 Err(_) => Ok(VerifyResult {
60 success: false,
61 capabilities: None,
62 error: Some("Timeout waiting for adapter response".to_string()),
63 }),
64 }
65}
66
67pub async fn verify_dap_adapter_tcp(
69 path: &Path,
70 args: &[String],
71 spawn_style: TcpSpawnStyle,
72) -> Result<VerifyResult> {
73 let (mut child, addr) = match spawn_style {
74 TcpSpawnStyle::TcpListen => {
75 let mut cmd = Command::new(path);
76 cmd.args(args)
77 .arg("--listen=127.0.0.1:0")
78 .stdin(Stdio::null())
79 .stdout(Stdio::piped())
80 .stderr(Stdio::piped());
81
82 let mut child = cmd.spawn().map_err(|e| {
83 Error::Internal(format!("Failed to spawn adapter: {}", e))
84 })?;
85
86 let stdout = child.stdout.take().ok_or_else(|| {
87 Error::Internal("Failed to get adapter stdout".to_string())
88 })?;
89
90 let mut stdout_reader = BufReader::new(stdout);
91 let mut line = String::new();
92
93 let listen_result = timeout(Duration::from_secs(10), async {
94 loop {
95 line.clear();
96 let bytes_read = stdout_reader.read_line(&mut line).await.map_err(|e| {
97 Error::Internal(format!("Failed to read adapter output: {}", e))
98 })?;
99
100 if bytes_read == 0 {
101 return Err(Error::Internal(
102 "Adapter exited before outputting listen address".to_string(),
103 ));
104 }
105
106 if let Some(addr) = parse_listen_address(&line) {
107 return Ok(addr);
108 }
109 }
110 })
111 .await;
112
113 let addr = match listen_result {
114 Ok(Ok(addr)) => addr,
115 Ok(Err(e)) => {
116 let _ = child.kill().await;
117 return Ok(VerifyResult {
118 success: false,
119 capabilities: None,
120 error: Some(e.to_string()),
121 });
122 }
123 Err(_) => {
124 let _ = child.kill().await;
125 return Ok(VerifyResult {
126 success: false,
127 capabilities: None,
128 error: Some("Timeout waiting for adapter to start listening".to_string()),
129 });
130 }
131 };
132
133 (child, addr)
134 }
135 TcpSpawnStyle::TcpPortArg => {
136 use std::net::TcpListener as StdTcpListener;
137
138 let listener = StdTcpListener::bind("127.0.0.1:0").map_err(|e| {
139 Error::Internal(format!("Failed to allocate port: {}", e))
140 })?;
141 let port = listener.local_addr().map_err(|e| {
142 Error::Internal(format!("Failed to get port: {}", e))
143 })?.port();
144 drop(listener);
145
146 let addr = format!("127.0.0.1:{}", port);
147
148 let mut cmd = Command::new(path);
149 let mut full_args = args.to_vec();
150 full_args.push(port.to_string());
151
152 cmd.args(&full_args)
153 .stdin(Stdio::null())
154 .stdout(Stdio::piped())
155 .stderr(Stdio::piped());
156
157 let child = cmd.spawn().map_err(|e| {
158 Error::Internal(format!("Failed to spawn adapter: {}", e))
159 })?;
160
161 (child, addr)
162 }
163 };
164
165 let stream = {
167 let mut last_error = String::new();
168 let mut delay = Duration::from_millis(100);
169 let max_delay = Duration::from_millis(1000);
170 let timeout_duration = Duration::from_secs(10);
171 let start = std::time::Instant::now();
172
173 loop {
174 match TcpStream::connect(&addr).await {
175 Ok(s) => break s,
176 Err(e) => {
177 last_error = e.to_string();
178 if start.elapsed() >= timeout_duration {
179 let _ = child.kill().await;
180 return Ok(VerifyResult {
181 success: false,
182 capabilities: None,
183 error: Some(format!("Failed to connect to {} after {:?}: {}", addr, timeout_duration, last_error)),
184 });
185 }
186 tokio::time::sleep(delay).await;
187 delay = std::cmp::min(delay * 2, max_delay);
188 }
189 }
190 }
191 };
192
193 let init_result = timeout(Duration::from_secs(5), send_initialize_tcp(stream)).await;
194
195 let _ = child.kill().await;
196
197 match init_result {
198 Ok(Ok(caps)) => Ok(VerifyResult {
199 success: true,
200 capabilities: Some(caps),
201 error: None,
202 }),
203 Ok(Err(e)) => Ok(VerifyResult {
204 success: false,
205 capabilities: None,
206 error: Some(e.to_string()),
207 }),
208 Err(_) => Ok(VerifyResult {
209 success: false,
210 capabilities: None,
211 error: Some("Timeout waiting for adapter response".to_string()),
212 }),
213 }
214}
215
216async fn spawn_adapter(path: &Path, args: &[String]) -> Result<Child> {
218 let child = Command::new(path)
219 .args(args)
220 .stdin(Stdio::piped())
221 .stdout(Stdio::piped())
222 .stderr(Stdio::piped()) .spawn()
224 .map_err(|e| Error::Internal(format!("Failed to spawn adapter: {}", e)))?;
225
226 Ok(child)
227}
228
229fn build_initialize_request() -> serde_json::Value {
231 serde_json::json!({
232 "seq": 1,
233 "type": "request",
234 "command": "initialize",
235 "arguments": {
236 "clientID": "debugger-cli",
237 "clientName": "debugger-cli",
238 "adapterID": "test",
239 "pathFormat": "path",
240 "linesStartAt1": true,
241 "columnsStartAt1": true,
242 "supportsRunInTerminalRequest": false
243 }
244 })
245}
246
247fn parse_initialize_response(response: &serde_json::Value) -> Result<DapCapabilities> {
249 if response.get("success").and_then(|v| v.as_bool()) != Some(true) {
251 let message = response
252 .get("message")
253 .and_then(|v| v.as_str())
254 .unwrap_or("Unknown error");
255 return Err(Error::Internal(format!("Initialize failed: {}", message)));
256 }
257
258 let body = response.get("body").cloned().unwrap_or_default();
260 Ok(DapCapabilities {
261 supports_configuration_done_request: body
262 .get("supportsConfigurationDoneRequest")
263 .and_then(|v| v.as_bool())
264 .unwrap_or(false),
265 supports_function_breakpoints: body
266 .get("supportsFunctionBreakpoints")
267 .and_then(|v| v.as_bool())
268 .unwrap_or(false),
269 supports_conditional_breakpoints: body
270 .get("supportsConditionalBreakpoints")
271 .and_then(|v| v.as_bool())
272 .unwrap_or(false),
273 supports_evaluate_for_hovers: body
274 .get("supportsEvaluateForHovers")
275 .and_then(|v| v.as_bool())
276 .unwrap_or(false),
277 })
278}
279
280async fn send_dap_message<W, R>(writer: &mut W, reader: &mut BufReader<R>, request: &serde_json::Value) -> Result<serde_json::Value>
282where
283 W: AsyncWriteExt + Unpin,
284 R: tokio::io::AsyncRead + Unpin,
285{
286 let body = serde_json::to_string(request)?;
288 let header = format!("Content-Length: {}\r\n\r\n", body.len());
289 writer.write_all(header.as_bytes()).await?;
290 writer.write_all(body.as_bytes()).await?;
291 writer.flush().await?;
292
293 let mut content_length: Option<usize> = None;
295 loop {
296 let mut header_line = String::new();
297 reader.read_line(&mut header_line).await?;
298 let trimmed = header_line.trim();
299
300 if trimmed.is_empty() {
302 break;
303 }
304
305 if let Some(len_str) = trimmed.strip_prefix("Content-Length:") {
307 content_length = len_str.trim().parse().ok();
308 }
309 }
311
312 let content_length = content_length
313 .ok_or_else(|| Error::Internal("Missing Content-Length in DAP response".to_string()))?;
314
315 let mut body = vec![0u8; content_length];
317 reader.read_exact(&mut body).await?;
318
319 let response: serde_json::Value = serde_json::from_slice(&body)?;
321 Ok(response)
322}
323
324async fn send_initialize(child: &mut Child) -> Result<DapCapabilities> {
326 let stdin = child.stdin.as_mut().ok_or_else(|| {
327 Error::Internal("Failed to get stdin".to_string())
328 })?;
329 let stdout = child.stdout.as_mut().ok_or_else(|| {
330 Error::Internal("Failed to get stdout".to_string())
331 })?;
332
333 let request = build_initialize_request();
334 let mut reader = BufReader::new(stdout);
335 let response = send_dap_message(stdin, &mut reader, &request).await?;
336 parse_initialize_response(&response)
337}
338
339async fn send_initialize_tcp(stream: TcpStream) -> Result<DapCapabilities> {
341 let (read_half, mut write_half) = tokio::io::split(stream);
342
343 let request = build_initialize_request();
344 let mut reader = BufReader::new(read_half);
345 let response = send_dap_message(&mut write_half, &mut reader, &request).await?;
346 parse_initialize_response(&response)
347}
348
349pub async fn verify_executable(path: &Path, version_arg: Option<&str>) -> Result<VerifyResult> {
351 let arg = version_arg.unwrap_or("--version");
352
353 let output = tokio::process::Command::new(path)
354 .arg(arg)
355 .output()
356 .await
357 .map_err(|e| Error::Internal(format!("Failed to run {}: {}", path.display(), e)))?;
358
359 if output.status.success() {
360 Ok(VerifyResult {
361 success: true,
362 capabilities: None,
363 error: None,
364 })
365 } else {
366 Ok(VerifyResult {
367 success: false,
368 capabilities: None,
369 error: Some(format!(
370 "Exit code: {}",
371 output.status.code().unwrap_or(-1)
372 )),
373 })
374 }
375}