use super::probe::MAX_REPLY_BYTES;
#[cfg_attr(not(unix), allow(dead_code))]
fn append_reply(replies: &mut Vec<u8>, chunk: &[u8]) -> bool {
let remaining = MAX_REPLY_BYTES.saturating_sub(replies.len());
replies.extend_from_slice(&chunk[..chunk.len().min(remaining)]);
replies.len() >= MAX_REPLY_BYTES
}
#[cfg(unix)]
pub(crate) fn exchange(
queries: &str,
budget: std::time::Duration,
done: impl Fn(&[u8]) -> bool,
) -> Option<Vec<u8>> {
use std::io::{Read, Write};
use rustix::termios::{self, OptionalActions, SpecialCodeIndex};
struct Restore<'t> {
tty: &'t std::fs::File,
saved: termios::Termios,
}
impl Drop for Restore<'_> {
fn drop(&mut self) {
let _ = termios::tcsetattr(self.tty, OptionalActions::Now, &self.saved);
}
}
let tty = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open("/dev/tty")
.ok()?;
let saved = termios::tcgetattr(&tty).ok()?;
let mut raw = saved.clone();
raw.make_raw();
raw.special_codes[SpecialCodeIndex::VMIN] = 0;
raw.special_codes[SpecialCodeIndex::VTIME] = 1;
termios::tcsetattr(&tty, OptionalActions::Now, &raw).ok()?;
let restore = Restore { tty: &tty, saved };
(&tty).write_all(queries.as_bytes()).ok()?;
(&tty).flush().ok()?;
let start = std::time::Instant::now();
let mut replies = Vec::new();
let mut chunk = [0u8; 512];
while start.elapsed() < budget && !done(&replies) {
let remaining = MAX_REPLY_BYTES.saturating_sub(replies.len());
if remaining == 0 {
break;
}
let read_len = remaining.min(chunk.len());
match (&tty).read(&mut chunk[..read_len]) {
Ok(0) => continue,
Ok(count) if append_reply(&mut replies, &chunk[..count]) => break,
Ok(_) => {}
Err(error) if error.kind() == std::io::ErrorKind::Interrupted => continue,
Err(_) => break,
}
}
drop(restore);
Some(replies)
}
#[cfg(not(unix))]
pub(crate) fn exchange(
_queries: &str,
_budget: std::time::Duration,
_done: impl Fn(&[u8]) -> bool,
) -> Option<Vec<u8>> {
None
}
#[cfg(test)]
#[path = "tests/query_tests.rs"]
mod tests;