use super::DebugService;
use async_trait::async_trait;
use parking_lot::Mutex;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
use tokio::process::{Child, ChildStdin, ChildStdout};
const REQUEST_TIMEOUT: Duration = Duration::from_secs(10);
pub struct DapClient {
child: Child,
stdin: ChildStdin,
stdout: BufReader<ChildStdout>,
seq: AtomicU64,
pub events: Arc<Mutex<Vec<serde_json::Value>>>,
}
impl std::fmt::Debug for DapClient {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DapClient")
.field("alive", &self.child.id().is_some())
.field("events", &self.events.lock().len())
.finish()
}
}
impl DapClient {
pub fn spawn(adapter: &[String]) -> Result<Self, String> {
use tokio::process::Command;
let (program, args) = adapter.split_first().ok_or("adapter command is empty")?;
let mut cmd = Command::new(program);
cmd.args(args)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.kill_on_drop(true);
let mut child = cmd.spawn().map_err(|e| format!("spawn adapter: {e}"))?;
let stdin = child.stdin.take().ok_or("adapter: no stdin")?;
let stdout = child.stdout.take().ok_or("adapter: no stdout")?;
Ok(Self {
child,
stdin,
stdout: BufReader::new(stdout),
seq: AtomicU64::new(0),
events: Arc::new(Mutex::new(Vec::new())),
})
}
fn next_seq(&self) -> u64 {
self.seq.fetch_add(1, Ordering::SeqCst) + 1
}
async fn write_frame(&mut self, value: &serde_json::Value) -> Result<(), String> {
let body = serde_json::to_vec(value).map_err(|e| format!("serialize frame: {e}"))?;
self.stdin
.write_all(format!("Content-Length: {}\r\n\r\n", body.len()).as_bytes())
.await
.map_err(|e| format!("adapter stdin write: {e}"))?;
self.stdin
.write_all(&body)
.await
.map_err(|e| format!("adapter stdin write: {e}"))?;
self.stdin
.flush()
.await
.map_err(|e| format!("adapter stdin flush: {e}"))
}
async fn read_frame(&mut self, deadline: Instant) -> Result<serde_json::Value, String> {
let mut content_length: Option<usize> = None;
loop {
let mut line = String::new();
let read = tokio::time::timeout_at(
tokio::time::Instant::from(deadline),
self.stdout.read_line(&mut line),
)
.await
.map_err(|_| "timed out reading adapter frame headers".to_string())?
.map_err(|e| format!("adapter stdout read: {e}"))?;
if read == 0 {
return Err("adapter closed the connection".to_string());
}
let trimmed = line.trim_end();
if trimmed.is_empty() {
break; }
if let Some(v) = trimmed.strip_prefix("Content-Length:") {
content_length = Some(
v.trim()
.parse()
.map_err(|_| "bad Content-Length header".to_string())?,
);
}
}
let length = content_length.ok_or("adapter frame missing Content-Length")?;
let mut body = vec![0u8; length];
tokio::time::timeout_at(
tokio::time::Instant::from(deadline),
self.stdout.read_exact(&mut body),
)
.await
.map_err(|_| "timed out reading adapter frame body".to_string())?
.map_err(|e| format!("adapter stdout read: {e}"))?;
serde_json::from_slice(&body).map_err(|e| format!("bad adapter frame JSON: {e}"))
}
pub async fn request(
&mut self,
command: &str,
arguments: &serde_json::Value,
) -> Result<serde_json::Value, String> {
let deadline = Instant::now() + REQUEST_TIMEOUT;
let seq = self.next_seq();
let frame = serde_json::json!({
"seq": seq,
"type": "request",
"command": command,
"arguments": arguments,
});
self.write_frame(&frame).await?;
loop {
if Instant::now() >= deadline {
return Err(format!("DAP request '{command}' timed out"));
}
let msg = self.read_frame(deadline).await?;
match msg.get("type").and_then(|t| t.as_str()) {
Some("response") => {
let req_seq = msg.get("request_seq").and_then(|s| s.as_u64());
if req_seq == Some(seq) {
let success = msg
.get("success")
.and_then(|s| s.as_bool())
.unwrap_or(false);
return if success {
Ok(msg.get("body").cloned().unwrap_or(serde_json::Value::Null))
} else {
Err(format!(
"DAP '{command}' failed: {}",
msg.get("message")
.and_then(|m| m.as_str())
.unwrap_or("unknown")
))
};
}
}
Some("event") => self.events.lock().push(msg),
_ => {}
}
}
}
pub async fn wait_for_event(
&mut self,
event: &str,
wait: Duration,
) -> Option<serde_json::Value> {
let deadline = Instant::now() + wait;
loop {
{
let events = self.events.lock();
if let Some(found) = events
.iter()
.find(|e| e.get("event").and_then(|v| v.as_str()) == Some(event))
{
return Some(found.clone());
}
}
if Instant::now() >= deadline {
return None;
}
match self.read_frame(deadline).await {
Ok(msg) => {
if msg.get("type").and_then(|t| t.as_str()) == Some("event") {
let is_match = msg.get("event").and_then(|v| v.as_str()) == Some(event);
self.events.lock().push(msg);
if is_match {
return self
.events
.lock()
.iter()
.find(|e| e.get("event").and_then(|v| v.as_str()) == Some(event))
.cloned();
}
}
}
Err(_) => return None,
}
}
}
async fn shutdown(&mut self) {
let _ = self
.request(
"disconnect",
&serde_json::json!({"terminateDebuggee": true}),
)
.await;
let _ = self.child.kill().await;
}
}
#[derive(Debug, Default)]
pub struct DapDebugService {
sessions: Mutex<HashMap<String, DapClient>>,
}
impl DapDebugService {
pub fn new() -> Self {
Self::default()
}
}
#[async_trait]
impl DebugService for DapDebugService {
async fn start(&self, config: &serde_json::Value) -> Result<String, String> {
let adapter: Vec<String> = config
.get("adapter")
.and_then(|a| a.as_array())
.map(|items| {
items
.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
})
.ok_or("config must carry an \"adapter\" command array")?;
let request = config
.get("request")
.and_then(|r| r.as_str())
.unwrap_or("launch");
let mut launch_args = config.clone();
if let Some(obj) = launch_args.as_object_mut() {
obj.remove("adapter");
obj.insert(
"request".to_string(),
serde_json::Value::String(request.to_string()),
);
}
let mut client = DapClient::spawn(&adapter)?;
client
.request(
"initialize",
&serde_json::json!({"adapterID": config.get("type").and_then(|t| t.as_str()).unwrap_or("oxi"), "clientID": "oxicode"}),
)
.await?;
client.request(request, &launch_args).await?;
let _ = client
.wait_for_event("stopped", Duration::from_secs(10))
.await;
let session = uuid::Uuid::new_v4().to_string();
self.sessions.lock().insert(session.clone(), client);
Ok(session)
}
async fn request(
&self,
session: &str,
command: &str,
args: &serde_json::Value,
) -> Result<serde_json::Value, String> {
let mut client = self
.sessions
.lock()
.remove(session)
.ok_or("unknown debug session")?;
let result = client.request(command, args).await;
self.sessions.lock().insert(session.to_string(), client);
result
}
async fn terminate(&self, session: &str) -> Result<(), String> {
let mut client = self
.sessions
.lock()
.remove(session)
.ok_or("unknown debug session")?;
let _ = client.request("terminate", &serde_json::json!({})).await;
client.shutdown().await;
Ok(())
}
}