Skip to main content

debugger/dap/
codec.rs

1//! DAP wire protocol codec
2//!
3//! The DAP protocol uses HTTP-style headers followed by JSON body:
4//! ```text
5//! Content-Length: <byte-length>\r\n
6//! \r\n
7//! <JSON body>
8//! ```
9
10use std::io;
11use tokio::io::{AsyncBufRead, AsyncBufReadExt, AsyncReadExt, AsyncWrite, AsyncWriteExt};
12
13use crate::common::Error;
14
15/// Read a DAP message from the stream
16///
17/// Parses the Content-Length header and reads the JSON body
18pub async fn read_message<R: AsyncBufRead + Unpin>(reader: &mut R) -> Result<String, Error> {
19    // Read headers line by line until we get an empty line
20    let mut content_length: Option<usize> = None;
21
22    loop {
23        let mut line = String::new();
24        let bytes_read = reader.read_line(&mut line).await.map_err(|e| {
25            if e.kind() == io::ErrorKind::UnexpectedEof {
26                Error::AdapterCrashed
27            } else {
28                Error::Io(e)
29            }
30        })?;
31
32        if bytes_read == 0 {
33            return Err(Error::AdapterCrashed);
34        }
35
36        // Empty line (just \r\n) signals end of headers
37        if line == "\r\n" || line == "\n" {
38            break;
39        }
40
41        // Parse Content-Length header
42        let line = line.trim();
43        if let Some(value) = line.strip_prefix("Content-Length:") {
44            content_length = Some(value.trim().parse().map_err(|_| {
45                Error::DapProtocol(format!("Invalid Content-Length: {}", value.trim()))
46            })?);
47        }
48        // Ignore other headers (like Content-Type)
49    }
50
51    let len = content_length.ok_or_else(|| {
52        Error::DapProtocol("Missing Content-Length header".to_string())
53    })?;
54
55    // Sanity check - 100MB should be plenty for any DAP message
56    if len > 100 * 1024 * 1024 {
57        return Err(Error::DapProtocol(format!(
58            "Content-Length too large: {} bytes",
59            len
60        )));
61    }
62
63    // Read the JSON body
64    let mut body = vec![0u8; len];
65    reader.read_exact(&mut body).await.map_err(|e| {
66        if e.kind() == io::ErrorKind::UnexpectedEof {
67            Error::AdapterCrashed
68        } else {
69            Error::Io(e)
70        }
71    })?;
72
73    String::from_utf8(body).map_err(|e| Error::DapProtocol(format!("Invalid UTF-8: {}", e)))
74}
75
76/// Write a DAP message to the stream
77///
78/// Adds the Content-Length header and writes the JSON body
79pub async fn write_message<W: AsyncWrite + Unpin>(
80    writer: &mut W,
81    json: &str,
82) -> Result<(), Error> {
83    let header = format!("Content-Length: {}\r\n\r\n", json.len());
84
85    writer.write_all(header.as_bytes()).await?;
86    writer.write_all(json.as_bytes()).await?;
87    writer.flush().await?;
88
89    Ok(())
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use std::io::Cursor;
96    use tokio::io::BufReader;
97
98    #[tokio::test]
99    async fn test_read_message() {
100        let data = b"Content-Length: 13\r\n\r\n{\"test\":true}";
101        let mut reader = BufReader::new(Cursor::new(data.to_vec()));
102
103        let result = read_message(&mut reader).await.unwrap();
104        assert_eq!(result, "{\"test\":true}");
105    }
106
107    #[tokio::test]
108    async fn test_read_message_with_extra_headers() {
109        let data = b"Content-Length: 13\r\nContent-Type: application/json\r\n\r\n{\"test\":true}";
110        let mut reader = BufReader::new(Cursor::new(data.to_vec()));
111
112        let result = read_message(&mut reader).await.unwrap();
113        assert_eq!(result, "{\"test\":true}");
114    }
115
116    #[tokio::test]
117    async fn test_write_message() {
118        let mut output = Vec::new();
119        write_message(&mut output, "{\"test\":true}").await.unwrap();
120
121        let expected = "Content-Length: 13\r\n\r\n{\"test\":true}";
122        assert_eq!(String::from_utf8(output).unwrap(), expected);
123    }
124}