use std::time::Duration;
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, SessionState, MAX_FRAME_LEN};
use tokio_util::codec::{Framed, LengthDelimitedCodec};
const STOP_CONFIRM_BOUND: Duration = Duration::from_secs(5);
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
}
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>,
label: &str,
) -> Result<String> {
let addr = device.unwrap_or_else(|| cfg.device.address());
let Ok(mut framed) = connect_raw(addr).await else {
return Ok(format!("{label} for {addr}: not running"));
};
send_frame(&mut framed, &BrokerRequest::Status).await?;
match recv_frame(&mut framed).await? {
BrokerResponse::StatusInfo { state, device: dev, .. } => {
Ok(format!("{label} for {dev}: {state}"))
}
other => Ok(format!("unexpected response: {other:?}")),
}
}
pub(in crate::commands) async fn query_persistent(addr: &str) -> Option<bool> {
let Ok(mut framed) = connect_raw(addr).await else {
tracing::debug!("broker: nothing reachable at {addr}");
return None;
};
send_frame(&mut framed, &BrokerRequest::Status).await.ok()?;
match recv_frame(&mut framed).await.ok()? {
BrokerResponse::StatusInfo { persistent, .. } => {
let kind = if persistent { "persistent daemon" } else { "ephemeral broker" };
tracing::debug!("broker: found {kind} already running at {addr}");
Some(persistent)
}
_ => None,
}
}
pub(in crate::commands) async fn query_state(addr: &str) -> Option<SessionState> {
let mut framed = connect_raw(addr).await.ok()?;
send_frame(&mut framed, &BrokerRequest::Status).await.ok()?;
match recv_frame(&mut framed).await.ok()? {
BrokerResponse::StatusInfo { state, .. } => Some(state),
_ => None,
}
}
pub(in crate::commands) async fn run_stop(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!("daemon for {addr}: not running"));
};
send_frame(&mut framed, &BrokerRequest::Shutdown).await?;
let resp = recv_frame(&mut framed).await?;
drop(framed);
match resp {
BrokerResponse::Ok => {
await_unreachable(addr).await;
Ok(format!("daemon for {addr}: stopping"))
}
BrokerResponse::Error(e) => Ok(format!("daemon for {addr}: {e}")),
other => Ok(format!("unexpected response: {other:?}")),
}
}
async fn await_unreachable(addr: &str) {
let Some(deadline) = tokio::time::Instant::now().checked_add(STOP_CONFIRM_BOUND) else {
return;
};
while tokio::time::Instant::now() < deadline {
if connect_raw(addr).await.is_err() {
return;
}
tokio::time::sleep(Duration::from_millis(25)).await;
}
}
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")
}