use crate::error::KamailioRpcError;
use crate::error::KamailioRpcError::{KamailioError, NoResponseOrError};
use libc::{mkfifo, mode_t, EACCES, EEXIST, ENOENT};
use log::{trace, warn};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::ffi::{c_int, CString};
use std::fs::Permissions;
use std::io;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use tokio::fs;
use tokio::fs::{set_permissions, OpenOptions};
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::sync::oneshot;
use uuid::Uuid;
pub mod error;
#[derive(Serialize, Deserialize, Debug)]
struct JsonRpcRequest {
#[serde(rename = "jsonrpc")]
json_rpc: String,
method: String,
id: i32,
reply_name: String,
}
#[derive(Serialize, Deserialize, Debug)]
struct JsonRpcResponse {
#[serde(rename = "jsonrpc")]
json_rpc: String,
result: Option<Value>,
error: Option<Value>,
id: i32,
}
pub async fn kamailio_rpc(kamailio_cmd: &str) -> Result<Value, KamailioRpcError> {
let fifo_id = Uuid::new_v4();
let name = format!("kamailio_receiver_{}", fifo_id);
let path = format!("/tmp/{}", name);
delete_fifo_if_exists(&path).await?;
create_fifo(&path, Some(0o666))?;
set_permissions(&path, Permissions::from_mode(0o666)).await?;
let request = JsonRpcRequest {
json_rpc: "2.0".to_string(),
method: kamailio_cmd.to_string(),
id: 1,
reply_name: name,
};
let request_json = serde_json::to_string(&request)?;
let response =
send_and_receive("/run/kamailio/kamailio_rpc.fifo", &path, &request_json).await?;
let response_json: JsonRpcResponse = serde_json::from_str(&response)?;
println!("Response received: {:?}", response_json);
delete_fifo_if_exists(&path).await?;
if let Some(result) = response_json.result {
return Ok(result);
} else if let Some(error) = response_json.error {
return Err(KamailioError(error));
}
Err(NoResponseOrError)
}
async fn send_and_receive(
send_fifo: &str,
receive_fifo: &str,
request: &str,
) -> Result<String, KamailioRpcError> {
let (tx, rx) = oneshot::channel();
let receive_fifo = receive_fifo.to_string();
tokio::spawn(async move {
match OpenOptions::new().read(true).open(&receive_fifo).await {
Ok(file) => {
let mut reader = BufReader::new(file);
let mut response = String::new();
loop {
match reader.read_line(&mut response).await {
Ok(0) => break,
Ok(_) => {}
Err(e) => warn!("Error reading from FIFO socket: {}", e),
}
}
if let Err(e) = tx.send(response) {
warn!("Failed to send received Kamailio response: {}", e);
};
}
Err(e) => {
warn!("Could not open the receiving FIFO {}: {}", receive_fifo, e);
return;
}
}
});
trace!("Sending request: {}", request);
fs::write(send_fifo, request).await?;
trace!("Request sent");
Ok(rx.await?)
}
async fn delete_fifo_if_exists(path: &String) -> Result<(), KamailioRpcError> {
match fs::remove_file(&path).await {
Ok(_) => trace!("Existing FIFO removed"),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
trace!("No need to delete existing FIFO")
}
Err(e) => {
return Err(e.into());
}
};
Ok(())
}
fn create_fifo<P: AsRef<Path>>(path: P, mode: Option<u32>) -> io::Result<()> {
let path = CString::new(path.as_ref().to_str().unwrap())?;
let mode = mode.unwrap_or(0o644);
let result: c_int = unsafe { mkfifo(path.as_ptr(), mode as mode_t) };
let result: i32 = result.into();
if result == 0 {
return Ok(());
}
let error = errno::errno();
return match error.0 {
EACCES => Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!("could not open {:?}: {}", path, error),
)),
EEXIST => Err(io::Error::new(
io::ErrorKind::AlreadyExists,
format!("could not open {:?}: {}", path, error),
)),
ENOENT => Err(io::Error::new(
io::ErrorKind::NotFound,
format!("could not open {:?}: {}", path, error),
)),
_ => Err(io::Error::new(
io::ErrorKind::Other,
format!("could not open {:?}: {}", path, error),
)),
};
}