use crate::error::DapzError;
#[derive(Debug)]
pub struct Frame {
pub header: Vec<u8>,
pub body: Vec<u8>,
}
pub fn parse_frame(raw: &[u8]) -> Result<Option<(Frame, usize)>, DapzError> {
let raw_str = std::str::from_utf8(raw)
.map_err(|_| DapzError::Protocol("non-UTF8 in frame header".into()))?;
let header_end = raw_str
.find("\r\n\r\n")
.or_else(|| raw_str.find("\n\n"))
.ok_or(DapzError::Protocol("missing header terminator".into()))?;
let header_part = &raw_str[..header_end];
let body_start = if raw_str[header_end..].starts_with("\r\n\r\n") {
header_end + 4
} else {
header_end + 2
};
let content_length = parse_content_length(header_part)?;
let total_len = body_start + content_length as usize;
if raw.len() < total_len {
return Ok(None); }
let body = raw[body_start..total_len].to_vec();
let header = raw[..body_start].to_vec();
Ok(Some((Frame { header, body }, total_len)))
}
pub fn serialize_frame(value: &serde_json::Value) -> Result<Vec<u8>, DapzError> {
let body = serde_json::to_string(value)?;
let header = format!("Content-Length: {}\r\n\r\n", body.len());
let mut bytes = header.into_bytes();
bytes.extend_from_slice(body.as_bytes());
Ok(bytes)
}
pub fn parse_content_length(header: &str) -> Result<u64, DapzError> {
for line in header.lines() {
let line = line.trim();
if let Some(value) = line.to_lowercase().strip_prefix("content-length:") {
let value = value.trim();
return value
.parse::<u64>()
.map_err(|_| DapzError::Protocol(format!("invalid Content-Length: {value}")));
}
}
Err(DapzError::Protocol("missing Content-Length header".into()))
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct DapMessage {
pub seq: i64,
#[serde(rename = "type")]
pub msg_type: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub command: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub event: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub request_seq: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub success: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub body: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub arguments: Option<serde_json::Value>,
}
impl DapMessage {
pub fn from_frame(bytes: &[u8]) -> Result<Self, DapzError> {
let (frame, _) =
parse_frame(bytes)?.ok_or_else(|| DapzError::Protocol("incomplete frame".into()))?;
let msg: DapMessage = serde_json::from_slice(&frame.body)?;
Ok(msg)
}
pub fn to_bytes(&self) -> Result<Vec<u8>, DapzError> {
let value = serde_json::to_value(self)?;
serialize_frame(&value)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_content_length() {
let header = "Content-Length: 42\r\n";
assert_eq!(parse_content_length(header).unwrap(), 42);
}
#[test]
fn test_missing_content_length() {
assert!(parse_content_length("").is_err());
}
#[test]
fn test_invalid_content_length_value() {
let header = "Content-Length: abc\r\n";
assert!(parse_content_length(header).is_err());
}
#[test]
fn test_roundtrip() {
let msg = DapMessage {
seq: 1,
msg_type: "request".into(),
command: Some("initialize".into()),
event: None,
request_seq: None,
success: None,
body: None,
arguments: None,
};
let bytes = msg.to_bytes().unwrap();
let parsed = DapMessage::from_frame(&bytes).unwrap();
assert_eq!(parsed.seq, 1);
assert_eq!(parsed.msg_type, "request");
assert_eq!(parsed.command.as_deref(), Some("initialize"));
}
#[test]
fn test_event_roundtrip() {
let msg = DapMessage {
seq: 2,
msg_type: "event".into(),
command: None,
event: Some("output".into()),
request_seq: None,
success: None,
body: None,
arguments: Some(serde_json::json!({
"category": "stdout",
"output": "hello world",
})),
};
let bytes = msg.to_bytes().unwrap();
let parsed = DapMessage::from_frame(&bytes).unwrap();
assert_eq!(parsed.event.as_deref(), Some("output"));
}
}