use std::{fmt, marker::PhantomData};
use crate::tor::control_client::{
commands::TorCommand,
error::TorClientError,
parsers,
parsers::ParseError,
response::ResponseLine,
};
pub struct ProtocolInfo<'a>(PhantomData<&'a ()>);
impl ProtocolInfo<'_> {
pub fn new() -> Self {
Self(PhantomData)
}
}
impl TorCommand for ProtocolInfo<'_> {
type Error = TorClientError;
type Output = ProtocolInfoResponse;
fn to_command_string(&self) -> Result<String, Self::Error> {
Ok("PROTOCOLINFO".to_string())
}
fn parse_responses(&self, responses: Vec<ResponseLine>) -> Result<Self::Output, Self::Error> {
let mut resp = ProtocolInfoResponse::default();
for response in responses {
if response.is_err() {
return Err(TorClientError::TorCommandFailed(response.value));
}
if !response.has_more {
continue;
}
let mut kv = response.value.splitn(2, ' ');
let key = match kv.next() {
Some(k) => k,
None => continue,
};
match key {
"PROTOCOLINFO" => {
let value = kv.next().ok_or(TorClientError::KeyValueNoValue)?;
resp.protocol_info_version = value.parse().map_err(ParseError::from)?;
},
"AUTH" => {
let value = kv.next().ok_or(TorClientError::KeyValueNoValue)?;
let kv = parsers::multi_key_value(value)?;
resp.auth_methods = ProtocolAuthMethods {
methods: kv
.get("METHODS")
.and_then(|m| m.first())
.map(|s| s.split(',').map(ToString::to_string).collect())
.unwrap_or_else(|| vec!["NULL".to_string()]),
cookie_file: kv.get("COOKIEFILE").and_then(|m| m.first()).map(|v| v.to_string()),
};
},
"VERSION" => {
let value = kv.next().ok_or(TorClientError::KeyValueNoValue)?;
resp.tor_version = value.to_string();
},
_ => continue,
}
}
Ok(resp)
}
}
impl fmt::Display for ProtocolInfo<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "PROTOCOLINFO")
}
}
#[derive(Debug, Clone, Default)]
pub struct ProtocolInfoResponse {
pub protocol_info_version: u16,
pub auth_methods: ProtocolAuthMethods,
pub tor_version: String,
}
#[derive(Debug, Clone, Default)]
pub struct ProtocolAuthMethods {
pub methods: Vec<String>,
pub cookie_file: Option<String>,
}