use crate::daemon::frame;
use crate::daemon::protocol::{Request, Response, WIRE_VERSION};
use anyhow::{bail, Context, Result};
use std::path::Path;
use std::time::Duration;
use tokio::io::{BufReader, BufWriter};
use tokio::net::UnixStream;
pub async fn connect(socket_path: &Path) -> Result<Client> {
let stream = tokio::time::timeout(Duration::from_secs(1), UnixStream::connect(socket_path))
.await
.with_context(|| {
format!(
"daemon client: connect timeout to {}",
socket_path.display()
)
})?
.with_context(|| format!("daemon client: connect to {}", socket_path.display()))?;
let (reader, writer) = stream.into_split();
let mut client = Client {
reader: BufReader::new(reader),
writer: BufWriter::new(writer),
};
client.send(&Request::Hello).await?;
match client.recv().await? {
Response::Hello { wire_version, .. } if wire_version == WIRE_VERSION => Ok(client),
Response::Hello {
wire_version,
keyhog_version,
..
} => bail!(
"daemon wire version mismatch: client expects {WIRE_VERSION}, daemon at {} reports {wire_version} (keyhog {keyhog_version}). Restart the daemon or pass --no-daemon.",
socket_path.display(),
),
other => bail!("daemon client: expected Hello reply, got {other:?}"),
}
}
pub struct Client {
reader: BufReader<tokio::net::unix::OwnedReadHalf>,
writer: BufWriter<tokio::net::unix::OwnedWriteHalf>,
}
impl Client {
pub async fn send(&mut self, request: &Request) -> Result<()> {
frame::write_request(&mut self.writer, request).await
}
pub async fn recv(&mut self) -> Result<Response> {
match frame::read_response(&mut self.reader).await? {
Some(r) => Ok(r),
None => bail!(
"daemon client: connection closed before response. \
The daemon may have crashed or been restarted mid-request. \
Try `keyhog daemon stop && keyhog daemon start`, or rerun \
the scan with `--no-daemon` to bypass the daemon path."
),
}
}
pub async fn round_trip(&mut self, request: &Request) -> Result<Response> {
self.send(request).await?;
self.recv().await
}
}