1use anyhow::{Context, Result};
2use serde_json::{Map, Value};
3use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
4
5#[derive(Debug)]
6pub enum BaresipMessage {
7 Event {
8 class: String,
9 type_: String,
10 param: String,
11 extra: Map<String, Value>,
12 },
13 Response {
14 ok: bool,
15 data: String,
16 #[allow(dead_code)]
17 token: Option<String>,
18 },
19}
20
21pub async fn read_message<R: AsyncRead + Unpin>(reader: &mut R) -> Result<BaresipMessage> {
23 let mut len_bytes: Vec<u8> = Vec::new();
25 loop {
26 let mut b = [0u8; 1];
27 reader
28 .read_exact(&mut b)
29 .await
30 .context("Connection closed")?;
31 if b[0] == b':' {
32 break;
33 }
34 if !b[0].is_ascii_digit() {
35 anyhow::bail!("Invalid netstring: expected digit, got 0x{:02x}", b[0]);
36 }
37 len_bytes.push(b[0]);
38 }
39
40 let len: usize = std::str::from_utf8(&len_bytes)
41 .context("Invalid netstring length (UTF-8)")?
42 .parse()
43 .context("Invalid netstring length (parse)")?;
44
45 let mut payload = vec![0u8; len + 1];
47 reader
48 .read_exact(&mut payload)
49 .await
50 .context("Connection closed reading payload")?;
51
52 if payload.last() != Some(&b',') {
53 anyhow::bail!("Invalid netstring: missing trailing ','");
54 }
55 payload.pop();
56
57 let json: Value = serde_json::from_slice(&payload).context("Invalid JSON in netstring")?;
58 parse_message(json)
59}
60
61pub async fn write_command<W: AsyncWrite + Unpin>(
63 writer: &mut W,
64 command: &str,
65 params: &str,
66) -> Result<()> {
67 let json = if params.is_empty() {
68 serde_json::json!({"command": command})
69 } else {
70 serde_json::json!({"command": command, "params": params})
71 };
72 let json_str = json.to_string();
73 let frame = format!("{}:{},", json_str.len(), json_str);
74 writer
75 .write_all(frame.as_bytes())
76 .await
77 .context("Failed to write command")?;
78 writer.flush().await.context("Failed to flush")?;
79 Ok(())
80}
81
82fn parse_message(json: Value) -> Result<BaresipMessage> {
83 let obj = json.as_object().context("Expected JSON object")?;
84
85 if obj.get("event").and_then(|v| v.as_bool()) == Some(true) {
86 let class = obj
87 .get("class")
88 .and_then(|v| v.as_str())
89 .unwrap_or("")
90 .to_string();
91 let type_ = obj
92 .get("type")
93 .and_then(|v| v.as_str())
94 .unwrap_or("")
95 .to_string();
96 let param = obj
97 .get("param")
98 .and_then(|v| v.as_str())
99 .unwrap_or("")
100 .to_string();
101 let mut extra = obj.clone();
102 extra.remove("event");
103 extra.remove("class");
104 extra.remove("type");
105 extra.remove("param");
106 return Ok(BaresipMessage::Event {
107 class,
108 type_,
109 param,
110 extra,
111 });
112 }
113
114 if obj.get("response").and_then(|v| v.as_bool()) == Some(true) {
115 let ok = obj.get("ok").and_then(|v| v.as_bool()).unwrap_or(false);
116 let data = obj
117 .get("data")
118 .and_then(|v| v.as_str())
119 .unwrap_or("")
120 .to_string();
121 let token = obj
122 .get("token")
123 .and_then(|v| v.as_str())
124 .map(|s| s.to_string());
125 return Ok(BaresipMessage::Response { ok, data, token });
126 }
127
128 anyhow::bail!("Unknown message: missing 'event' or 'response' field")
129}