//! RPC support for client tor-proto objects. 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}, }; /// RPC method that returns the tunnel for a given object. /// /// This is currently implemented for Data streams, /// but could in the future be implemented for other types. /// /// # In the Arti RPC System /// /// Return a tunnel associated with a given object. /// /// (A tunnel is a collection of one or more circuits /// used to transmit data.) /// /// Gives an error if the object is not associated with a tunnel, /// or if the tunnel was closed. /// /// The returned ObjectId is a handle to a `ClientTunnel`. /// The caller should drop this ObjectId when they are done with the ClientTunnel. #[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; } /// RPC method to describe the path for an object. /// /// This is currently implemented for [`ClientTunnel`], /// but could in the future be implemented for other types. /// /// # In the Arti RPC System /// /// Describe the path(s) of a tunnel through the Tor network. /// /// (Because of [Conflux], a tunnel can have multiple paths. /// This method describes the members of each one.) /// /// If `include_deprecated_ids` is true, /// the output includes deprecated node identity types, /// including the RSA identity. /// Otherwise, only non-deprecated identities are included. /// /// [Conflux]: https://spec.torproject.org/proposals/329-traffic-splitting.html #[derive(Debug, serde::Deserialize, Deftly)] #[derive_deftly(rpc::DynMethod)] #[deftly(rpc(method_name = "arti:describe_path"))] #[non_exhaustive] pub struct DescribePath { /// If true, the output will include deprecated node identity types. #[serde(default)] include_deprecated_ids: bool, } impl rpc::RpcMethod for DescribePath { type Output = PathDescription; type Update = rpc::NoUpdates; } /// A RPC-level description for a single entry in a tunnel or circuit path. #[derive(serde::Serialize, Clone, Debug)] #[non_exhaustive] #[serde(rename_all = "snake_case")] pub enum PathEntry { /// A hop not corresponding to a known relay. /// /// Typically, this is the join point for a rendezvous circuit for /// communicating with or as an onion service. VirtualHop {}, /// Return KnownRelay { /// A set of IDs for this Tor relay. /// /// Each ID represents a long-term public key used to identify the relay. /// Deprecated ID types are not included, unless they were specifically requested. ids: RelayIds, /// A list of the relay's addresses. addrs: Vec, }, } /// Serializable container of relay identities. /// /// Differs from [`tor_linkspec::RelayIds`] in is serialize behavior; /// See . /// /// Every relay will have at least one ID. On the current Tor network /// as of April 2026, every relay has an Ed25519 ID. /// (This may not always be the case in the future; /// for example, if we migrate to ML-DSA keys, /// we may eventually retire Ed25519 IDs.) #[derive(Clone, Debug, serde::Serialize)] pub struct RelayIds { /// Copy of the ed25519 id from the underlying ChanTarget. #[serde(rename = "ed25519", skip_serializing_if = "Option::is_none")] ed_identity: Option, /// Copy of the rsa id from the underlying ChanTarget. /// /// This is a deprecated ID type. #[serde(rename = "rsa", skip_serializing_if = "Option::is_none")] rsa_identity: Option, } /// A description of a tunnel's path. /// /// Note that tunnels are potentially made of multiple circuits, even though /// Arti (as of April 2026) does not yet build Conflux tunnels. /// Therefore, users should make sure to handle multi-path tunnels here. #[derive(serde::Serialize, Clone, Debug)] pub struct PathDescription { /// The entries in a given tunnel's path(s). /// /// Within each path, entries are ordered from first (closest to the client) /// to last (farthest from the client). /// /// Since tunnels can have multiple paths, each path is identified with a string. /// The actual content of these strings is not documented, /// but the string identifying each tunnel is stable. /// /// (That is, if you query a tunnel's path description twice, /// each path will have the same identifying string both times. /// If you get a new identifying string, /// it represents a path that was previously not part of the tunnel.) path: HashMap>, } impl PathEntry { /// Construct a PathEntry from a PathEntry returned by a circuit. 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 } } } /// Helper: Return the [`ClientTunnel`] for a [`ClientDataStreamCtrl`], /// or an RPC error if the stream isn't attached to a tunnel. fn client_stream_tunnel(stream: &ClientDataStreamCtrl) -> Result, rpc::RpcError> { stream.tunnel().ok_or_else(|| { rpc::RpcError::new( "Stream was not attached to a tunnel".to_string(), rpc::RpcErrorKind::RequestError, ) }) } /// Helper: Return a [`PathDescription`] for a [`ClientTunnel`] 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 } } /// Implementation function: implements GetTunnel on ClientDataStreamCtrl. async fn client_data_stream_ctrl_get_tunnel( target: Arc, _method: Box, ctx: Arc, ) -> Result { let tunnel: Arc = client_stream_tunnel(&target)? as _; let id = ctx.register_owned(tunnel); Ok(SingleIdResponse::from(id)) } /// Implementation function: implements DescribePath on a ClientTunnel. async fn client_tunnel_describe_path( target: Arc, method: Box, _ctx: Arc, ) -> Result { Ok(tunnel_path(&target, &method)) } /// Implementation function: implements DescribePath on a ClientDataStreamCtrl. async fn client_data_stream_ctrl_describe_path( target: Arc, method: Box, _ctx: Arc, ) -> Result { 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; }