Skip to main content

ringo_core/
client.rs

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
21/// Read one netstring-framed JSON message from `reader`.
22pub async fn read_message<R: AsyncRead + Unpin>(reader: &mut R) -> Result<BaresipMessage> {
23    // Read ASCII decimal length digits until ':'
24    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    // Cap the frame size so a bogus/huge length can't trigger a giant allocation.
46    // baresip's ctrl_tcp messages are tiny; 16 MiB is far above any real one.
47    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    // Read payload + trailing ','
53    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
68/// Encode `command` + `params` as a netstring and write to `writer`.
69pub 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}