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    // Read payload + trailing ','
46    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
61/// Encode `command` + `params` as a netstring and write to `writer`.
62pub 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}