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 const MAX_NETSTRING_LEN: usize = 16 * 1024 * 1024;
48 if len > MAX_NETSTRING_LEN {
49 anyhow::bail!("Netstring too large: {len} bytes (max {MAX_NETSTRING_LEN})");
50 }
51
52 let mut payload = vec![0u8; len + 1];
54 reader
55 .read_exact(&mut payload)
56 .await
57 .context("Connection closed reading payload")?;
58
59 if payload.last() != Some(&b',') {
60 anyhow::bail!("Invalid netstring: missing trailing ','");
61 }
62 payload.pop();
63
64 let json: Value = serde_json::from_slice(&payload).context("Invalid JSON in netstring")?;
65 parse_message(json)
66}
67
68pub async fn write_command<W: AsyncWrite + Unpin>(
70 writer: &mut W,
71 command: &str,
72 params: &str,
73) -> Result<()> {
74 let json = if params.is_empty() {
75 serde_json::json!({"command": command})
76 } else {
77 serde_json::json!({"command": command, "params": params})
78 };
79 let json_str = json.to_string();
80 let frame = format!("{}:{},", json_str.len(), json_str);
81 writer
82 .write_all(frame.as_bytes())
83 .await
84 .context("Failed to write command")?;
85 writer.flush().await.context("Failed to flush")?;
86 Ok(())
87}
88
89fn parse_message(json: Value) -> Result<BaresipMessage> {
90 let obj = json.as_object().context("Expected JSON object")?;
91
92 if obj.get("event").and_then(|v| v.as_bool()) == Some(true) {
93 let class = obj
94 .get("class")
95 .and_then(|v| v.as_str())
96 .unwrap_or("")
97 .to_string();
98 let type_ = obj
99 .get("type")
100 .and_then(|v| v.as_str())
101 .unwrap_or("")
102 .to_string();
103 let param = obj
104 .get("param")
105 .and_then(|v| v.as_str())
106 .unwrap_or("")
107 .to_string();
108 let mut extra = obj.clone();
109 extra.remove("event");
110 extra.remove("class");
111 extra.remove("type");
112 extra.remove("param");
113 return Ok(BaresipMessage::Event {
114 class,
115 type_,
116 param,
117 extra,
118 });
119 }
120
121 if obj.get("response").and_then(|v| v.as_bool()) == Some(true) {
122 let ok = obj.get("ok").and_then(|v| v.as_bool()).unwrap_or(false);
123 let data = obj
124 .get("data")
125 .and_then(|v| v.as_str())
126 .unwrap_or("")
127 .to_string();
128 let token = obj
129 .get("token")
130 .and_then(|v| v.as_str())
131 .map(|s| s.to_string());
132 return Ok(BaresipMessage::Response { ok, data, token });
133 }
134
135 anyhow::bail!("Unknown message: missing 'event' or 'response' field")
136}