use std::net::SocketAddr;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::TcpListener;
use tokio::task::JoinHandle;
#[derive(Default)]
pub struct MockOptions {
pub reject_auth: bool,
}
pub struct MockTorControlServer {
addr: SocketAddr,
_handle: JoinHandle<()>,
}
impl MockTorControlServer {
pub async fn start() -> Self {
Self::start_with_options(MockOptions::default()).await
}
pub async fn start_with_options(options: MockOptions) -> Self {
let listener = TcpListener::bind("127.0.0.1:0")
.await
.expect("bind mock control");
let addr = listener.local_addr().expect("local addr");
let handle = tokio::spawn(async move {
let (stream, _) = listener.accept().await.expect("accept");
let (reader, mut writer) = stream.into_split();
let mut reader = BufReader::new(reader);
let mut line = String::new();
let mut authenticated = false;
loop {
line.clear();
let n = match reader.read_line(&mut line).await {
Ok(n) => n,
Err(_) => break,
};
if n == 0 {
break;
}
let cmd = line.trim();
if cmd.starts_with("AUTHENTICATE") {
if options.reject_auth {
let _ = writer.write_all(b"515 Authentication failed\r\n").await;
} else {
authenticated = true;
let _ = writer.write_all(b"250 OK\r\n").await;
}
} else if !authenticated {
let _ = writer.write_all(b"514 Authentication required\r\n").await;
} else if cmd.starts_with("GETINFO status/bootstrap-phase") {
let _ = writer.write_all(
b"250-status/bootstrap-phase=NOTICE BOOTSTRAP PROGRESS=100 TAG=done SUMMARY=\"Done\"\r\n250 OK\r\n",
).await;
} else if cmd.starts_with("GETINFO status/circuit-established") {
let _ = writer
.write_all(b"250-status/circuit-established=1\r\n250 OK\r\n")
.await;
} else if cmd.starts_with("GETINFO traffic/read") {
let _ = writer
.write_all(b"250-traffic/read=1048576\r\n250 OK\r\n")
.await;
} else if cmd.starts_with("GETINFO traffic/written") {
let _ = writer
.write_all(b"250-traffic/written=524288\r\n250 OK\r\n")
.await;
} else if cmd.starts_with("GETINFO network-liveness") {
let _ = writer
.write_all(b"250-network-liveness=up\r\n250 OK\r\n")
.await;
} else if cmd.starts_with("GETINFO version") {
let _ = writer
.write_all(b"250-version=0.4.8.10\r\n250 OK\r\n")
.await;
} else if cmd.starts_with("GETINFO dormant") {
let _ = writer.write_all(b"250-dormant=0\r\n250 OK\r\n").await;
} else if cmd.starts_with("GETINFO net/listeners/socks") {
let _ = writer
.write_all(b"250-net/listeners/socks=\"127.0.0.1:9050\"\r\n250 OK\r\n")
.await;
} else {
let _ = writer.write_all(b"510 Unrecognized command\r\n").await;
}
let _ = writer.flush().await;
}
});
Self {
addr,
_handle: handle,
}
}
pub fn addr(&self) -> SocketAddr {
self.addr
}
}