Skip to main content

kamailio_rpc/
lib.rs

1use crate::error::KamailioRpcError;
2use crate::error::KamailioRpcError::{KamailioError, NoResponseOrError};
3use libc::{mkfifo, mode_t, EACCES, EEXIST, ENOENT};
4use log::{trace, warn};
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use std::ffi::{c_int, CString};
8use std::fs::Permissions;
9use std::io;
10use std::os::unix::fs::PermissionsExt;
11use std::path::Path;
12use tokio::fs;
13use tokio::fs::{set_permissions, OpenOptions};
14use tokio::io::{AsyncBufReadExt, BufReader};
15use tokio::sync::oneshot;
16use uuid::Uuid;
17
18pub mod error;
19
20#[derive(Serialize, Deserialize, Debug)]
21struct JsonRpcRequest {
22    #[serde(rename = "jsonrpc")]
23    json_rpc: String,
24    method: String,
25    id: i32,
26    reply_name: String,
27}
28
29#[derive(Serialize, Deserialize, Debug)]
30struct JsonRpcResponse {
31    #[serde(rename = "jsonrpc")]
32    json_rpc: String,
33    result: Option<Value>,
34    error: Option<Value>,
35    id: i32,
36}
37
38pub async fn kamailio_rpc(kamailio_cmd: &str) -> Result<Value, KamailioRpcError> {
39    let fifo_id = Uuid::new_v4();
40    let name = format!("kamailio_receiver_{}", fifo_id);
41    let path = format!("/tmp/{}", name);
42
43    delete_fifo_if_exists(&path).await?;
44
45    create_fifo(&path, Some(0o666))?;
46    set_permissions(&path, Permissions::from_mode(0o666)).await?;
47
48    let request = JsonRpcRequest {
49        json_rpc: "2.0".to_string(),
50        method: kamailio_cmd.to_string(),
51        id: 1,
52        reply_name: name,
53    };
54
55    let request_json = serde_json::to_string(&request)?;
56
57    let response =
58        send_and_receive("/run/kamailio/kamailio_rpc.fifo", &path, &request_json).await?;
59    let response_json: JsonRpcResponse = serde_json::from_str(&response)?;
60    println!("Response received: {:?}", response_json);
61
62    delete_fifo_if_exists(&path).await?;
63
64    if let Some(result) = response_json.result {
65        return Ok(result);
66    } else if let Some(error) = response_json.error {
67        return Err(KamailioError(error));
68    }
69
70    Err(NoResponseOrError)
71}
72
73async fn send_and_receive(
74    send_fifo: &str,
75    receive_fifo: &str,
76    request: &str,
77) -> Result<String, KamailioRpcError> {
78    let (tx, rx) = oneshot::channel();
79
80    // listen for response
81    let receive_fifo = receive_fifo.to_string();
82    tokio::spawn(async move {
83        match OpenOptions::new().read(true).open(&receive_fifo).await {
84            Ok(file) => {
85                let mut reader = BufReader::new(file);
86                let mut response = String::new();
87
88                loop {
89                    match reader.read_line(&mut response).await {
90                        Ok(0) => break,
91                        Ok(_) => {}
92                        Err(e) => warn!("Error reading from FIFO socket: {}", e),
93                    }
94                }
95
96                if let Err(e) = tx.send(response) {
97                    warn!("Failed to send received Kamailio response: {}", e);
98                };
99            }
100            Err(e) => {
101                warn!("Could not open the receiving FIFO {}: {}", receive_fifo, e);
102                return;
103            }
104        }
105    });
106
107    trace!("Sending request: {}", request);
108
109    fs::write(send_fifo, request).await?;
110    trace!("Request sent");
111
112    Ok(rx.await?)
113}
114
115async fn delete_fifo_if_exists(path: &String) -> Result<(), KamailioRpcError> {
116    match fs::remove_file(&path).await {
117        Ok(_) => trace!("Existing FIFO removed"),
118        Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
119            trace!("No need to delete existing FIFO")
120        }
121        Err(e) => {
122            return Err(e.into());
123        }
124    };
125    Ok(())
126}
127
128fn create_fifo<P: AsRef<Path>>(path: P, mode: Option<u32>) -> io::Result<()> {
129    let path = CString::new(path.as_ref().to_str().unwrap())?;
130    let mode = mode.unwrap_or(0o644);
131    let result: c_int = unsafe { mkfifo(path.as_ptr(), mode as mode_t) };
132
133    let result: i32 = result.into();
134    if result == 0 {
135        return Ok(());
136    }
137
138    let error = errno::errno();
139    return match error.0 {
140        EACCES => Err(io::Error::new(
141            io::ErrorKind::PermissionDenied,
142            format!("could not open {:?}: {}", path, error),
143        )),
144        EEXIST => Err(io::Error::new(
145            io::ErrorKind::AlreadyExists,
146            format!("could not open {:?}: {}", path, error),
147        )),
148        ENOENT => Err(io::Error::new(
149            io::ErrorKind::NotFound,
150            format!("could not open {:?}: {}", path, error),
151        )),
152        _ => Err(io::Error::new(
153            io::ErrorKind::Other,
154            format!("could not open {:?}: {}", path, error),
155        )),
156    };
157}