use anyhow::{Context, Result};
use bytes::Bytes;
use config::Config;
use futures::{SinkExt as _, StreamExt as _};
use interprocess::local_socket::{tokio::Stream as LocalStream, ConnectOptions};
use ipc::{BrokerRequest, BrokerResponse, MAX_FRAME_LEN};
use tokio_util::codec::{Framed, LengthDelimitedCodec};
pub(super) async fn send_request(addr: &str, req: BrokerRequest) -> Result<BrokerResponse> {
let mut framed = connect_raw(addr).await?;
send_frame(&mut framed, &req).await?;
recv_frame(&mut framed).await
}
pub(super) async fn connect_raw(addr: &str) -> Result<Framed<LocalStream, LengthDelimitedCodec>> {
let name = config::broker_abstract_name(addr).context("building broker socket name")?;
let stream =
ConnectOptions::new().name(name).connect_tokio().await.context("connecting to broker")?;
let codec = LengthDelimitedCodec::builder().max_frame_length(MAX_FRAME_LEN).new_codec();
Ok(Framed::new(stream, codec))
}
pub(in crate::commands) async fn run_status(cfg: &Config, device: Option<&str>) -> Result<String> {
let addr = device.unwrap_or_else(|| cfg.device.address());
let Ok(mut framed) = connect_raw(addr).await else {
return Ok(format!("broker for {addr}: not running"));
};
send_frame(&mut framed, &BrokerRequest::Status).await?;
match recv_frame(&mut framed).await? {
BrokerResponse::StatusInfo { state, device: dev } => {
Ok(format!("broker for {dev}: {state}"))
}
other => Ok(format!("unexpected response: {other:?}")),
}
}
pub(in crate::commands) async fn send_frame<T: tokio::io::AsyncWrite + Unpin>(
framed: &mut Framed<T, LengthDelimitedCodec>,
req: &BrokerRequest,
) -> Result<()> {
let bytes = Bytes::from(serde_json::to_vec(req).context("serialising request")?);
framed.send(bytes).await.context("sending request frame")
}
async fn recv_frame<T: tokio::io::AsyncRead + Unpin>(
framed: &mut Framed<T, LengthDelimitedCodec>,
) -> Result<BrokerResponse> {
let frame = framed
.next()
.await
.ok_or_else(|| anyhow::anyhow!("broker closed connection without sending a response"))?
.context("reading response frame")?;
serde_json::from_slice(&frame).context("deserialising response")
}