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 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}