use std::{collections::HashMap, net::SocketAddr, sync::Arc};
use derive_deftly::Deftly;
use tor_linkspec::{HasAddrs, HasRelayIds};
use tor_llcrypto::pk;
use tor_rpcbase::{self as rpc, SingleIdResponse};
use crate::{
ClientTunnel,
client::stream::{ClientDataStreamCtrl, ClientStreamCtrl},
};
#[derive(Debug, serde::Deserialize, Deftly)]
#[derive_deftly(rpc::DynMethod)]
#[deftly(rpc(method_name = "arti:get_tunnel"))]
#[non_exhaustive]
pub struct GetTunnel {}
impl rpc::RpcMethod for GetTunnel {
type Output = rpc::SingleIdResponse;
type Update = rpc::NoUpdates;
}
#[derive(Debug, serde::Deserialize, Deftly)]
#[derive_deftly(rpc::DynMethod)]
#[deftly(rpc(method_name = "arti:describe_path"))]
#[non_exhaustive]
pub struct DescribePath {
#[serde(default)]
include_deprecated_ids: bool,
}
impl rpc::RpcMethod for DescribePath {
type Output = PathDescription;
type Update = rpc::NoUpdates;
}
#[derive(serde::Serialize, Clone, Debug)]
#[non_exhaustive]
#[serde(rename_all = "snake_case")]
pub enum PathEntry {
VirtualHop {},
KnownRelay {
ids: RelayIds,
addrs: Vec<SocketAddr>,
},
}
#[derive(Clone, Debug, serde::Serialize)]
pub struct RelayIds {
#[serde(rename = "ed25519", skip_serializing_if = "Option::is_none")]
ed_identity: Option<pk::ed25519::Ed25519Identity>,
#[serde(rename = "rsa", skip_serializing_if = "Option::is_none")]
rsa_identity: Option<pk::rsa::RsaIdentity>,
}
#[derive(serde::Serialize, Clone, Debug)]
pub struct PathDescription {
path: HashMap<String, Vec<PathEntry>>,
}
impl PathEntry {
fn from_client_entry(
detail: &crate::client::circuit::PathEntry,
command: &DescribePath,
) -> Self {
let Some(owned_chan_target) = detail.as_chan_target() else {
return PathEntry::VirtualHop {};
};
let ids = tor_linkspec::RelayIds::from_relay_ids(owned_chan_target);
let ids = RelayIds {
ed_identity: ids.ed_identity().cloned(),
rsa_identity: if command.include_deprecated_ids {
ids.rsa_identity().cloned()
} else {
None
},
};
let addrs = owned_chan_target.addrs().collect();
PathEntry::KnownRelay { ids, addrs }
}
}
fn client_stream_tunnel(stream: &ClientDataStreamCtrl) -> Result<Arc<ClientTunnel>, rpc::RpcError> {
stream.tunnel().ok_or_else(|| {
rpc::RpcError::new(
"Stream was not attached to a tunnel".to_string(),
rpc::RpcErrorKind::RequestError,
)
})
}
fn tunnel_path(tunnel: &ClientTunnel, method: &DescribePath) -> PathDescription {
let path = tunnel
.tagged_paths()
.into_iter()
.map(|(id, path)| {
let id = id.display_chan_circ().to_string();
let path = path
.iter()
.map(|hop| PathEntry::from_client_entry(hop, method))
.collect();
(id, path)
})
.collect();
PathDescription { path }
}
async fn client_data_stream_ctrl_get_tunnel(
target: Arc<ClientDataStreamCtrl>,
_method: Box<GetTunnel>,
ctx: Arc<dyn rpc::Context>,
) -> Result<rpc::SingleIdResponse, rpc::RpcError> {
let tunnel: Arc<dyn rpc::Object> = client_stream_tunnel(&target)? as _;
let id = ctx.register_owned(tunnel);
Ok(SingleIdResponse::from(id))
}
async fn client_tunnel_describe_path(
target: Arc<ClientTunnel>,
method: Box<DescribePath>,
_ctx: Arc<dyn rpc::Context>,
) -> Result<PathDescription, rpc::RpcError> {
Ok(tunnel_path(&target, &method))
}
async fn client_data_stream_ctrl_describe_path(
target: Arc<ClientDataStreamCtrl>,
method: Box<DescribePath>,
_ctx: Arc<dyn rpc::Context>,
) -> Result<PathDescription, rpc::RpcError> {
let tunnel = client_stream_tunnel(&target)?;
Ok(tunnel_path(&tunnel, &method))
}
rpc::static_rpc_invoke_fn! {
client_data_stream_ctrl_get_tunnel;
client_tunnel_describe_path;
client_data_stream_ctrl_describe_path;
}