Skip to main content

hx_remote/
client.rs

1use crate::{SocketRequest, SocketResponse};
2use std::io::{self, BufRead, BufReader, ErrorKind, Read, Write};
3use std::net::Shutdown;
4use std::os::unix::net::UnixStream;
5use std::path::Path;
6use std::time::Duration;
7
8const RESPONSE_LIMIT: u64 = 64 * 1024;
9
10pub fn socket_is_listening(socket_path: &Path) -> io::Result<bool> {
11    match UnixStream::connect(socket_path) {
12        Ok(_) => Ok(true),
13        Err(error)
14            if matches!(
15                error.kind(),
16                ErrorKind::NotFound | ErrorKind::ConnectionRefused
17            ) || error.raw_os_error() == Some(libc::ENOTSOCK) =>
18        {
19            Ok(false)
20        }
21        Err(error) => Err(io::Error::new(
22            error.kind(),
23            format!("cannot check {}: {error}", socket_path.display()),
24        )),
25    }
26}
27
28pub fn send_socket_request(socket_path: &Path, request: &SocketRequest) -> io::Result<String> {
29    let mut stream = UnixStream::connect(socket_path).map_err(|error| {
30        io::Error::new(
31            error.kind(),
32            format!(
33                "cannot connect to {}: {error}; is a receiving Helix session running?",
34                socket_path.display()
35            ),
36        )
37    })?;
38    stream.set_read_timeout(Some(Duration::from_secs(10)))?;
39    stream.set_write_timeout(Some(Duration::from_secs(10)))?;
40
41    serde_json::to_writer(&mut stream, request)
42        .map_err(|error| io::Error::new(ErrorKind::InvalidData, error))?;
43    stream.write_all(b"\n")?;
44    stream.flush()?;
45    stream.shutdown(Shutdown::Write)?;
46
47    let mut response_bytes = Vec::new();
48    BufReader::new(stream)
49        .take(RESPONSE_LIMIT + 1)
50        .read_until(b'\n', &mut response_bytes)?;
51    if response_bytes.len() as u64 > RESPONSE_LIMIT {
52        return Err(io::Error::new(
53            ErrorKind::InvalidData,
54            "bridge response was unexpectedly large",
55        ));
56    }
57    if response_bytes.is_empty() {
58        return Err(io::Error::new(
59            ErrorKind::UnexpectedEof,
60            "bridge closed the connection without a response",
61        ));
62    }
63
64    let response: SocketResponse = serde_json::from_slice(&response_bytes)
65        .map_err(|error| io::Error::new(ErrorKind::InvalidData, error))?;
66    if response.ok {
67        Ok(response.message)
68    } else {
69        Err(io::Error::other(response.message))
70    }
71}