1
//! RPC support for client tor-proto objects.
2

            
3
use std::{collections::HashMap, net::SocketAddr, sync::Arc};
4

            
5
use derive_deftly::Deftly;
6
use tor_linkspec::{HasAddrs, HasRelayIds};
7
use tor_llcrypto::pk;
8
use tor_rpcbase::{self as rpc, SingleIdResponse};
9

            
10
use crate::{
11
    ClientTunnel,
12
    client::stream::{ClientDataStreamCtrl, ClientStreamCtrl},
13
};
14

            
15
/// RPC method that returns the tunnel for a given object.
16
///
17
/// This is currently implemented for Data streams,
18
/// but could in the future be implemented for other types.
19
///
20
/// # In the Arti RPC System
21
///
22
/// Return a tunnel associated with a given object.
23
///
24
/// (A tunnel is a collection of one or more circuits
25
/// used to transmit data.)
26
///
27
/// Gives an error if the object is not associated with a tunnel,
28
/// or if the tunnel was closed.
29
///
30
/// The returned ObjectId is a handle to a `ClientTunnel`.
31
/// The caller should drop this ObjectId when they are done with the ClientTunnel.
32
#[derive(Debug, serde::Deserialize, Deftly)]
33
#[derive_deftly(rpc::DynMethod)]
34
#[deftly(rpc(method_name = "arti:get_tunnel"))]
35
#[non_exhaustive]
36
pub struct GetTunnel {}
37

            
38
impl rpc::RpcMethod for GetTunnel {
39
    type Output = rpc::SingleIdResponse;
40
    type Update = rpc::NoUpdates;
41
}
42

            
43
/// RPC method to describe the path for an object.
44
///
45
/// This is currently implemented for [`ClientTunnel`],
46
/// but could in the future be implemented for other types.
47
///
48
/// # In the Arti RPC System
49
///
50
/// Describe the path(s) of a tunnel through the Tor network.
51
///
52
/// (Because of [Conflux], a tunnel can have multiple paths.
53
/// This method describes the members of each one.)
54
///
55
/// If `include_deprecated_ids` is true,
56
/// the output includes deprecated node identity types,
57
/// including the RSA identity.
58
/// Otherwise, only non-deprecated identities are included.
59
///
60
/// [Conflux]: https://spec.torproject.org/proposals/329-traffic-splitting.html
61
#[derive(Debug, serde::Deserialize, Deftly)]
62
#[derive_deftly(rpc::DynMethod)]
63
#[deftly(rpc(method_name = "arti:describe_path"))]
64
#[non_exhaustive]
65
pub struct DescribePath {
66
    /// If true, the output will include deprecated node identity types.
67
    #[serde(default)]
68
    include_deprecated_ids: bool,
69
}
70

            
71
impl rpc::RpcMethod for DescribePath {
72
    type Output = PathDescription;
73
    type Update = rpc::NoUpdates;
74
}
75

            
76
/// A RPC-level description for a single entry in a tunnel or circuit path.
77
#[derive(serde::Serialize, Clone, Debug)]
78
#[non_exhaustive]
79
#[serde(rename_all = "snake_case")]
80
pub enum PathEntry {
81
    /// A hop not corresponding to a known relay.
82
    ///
83
    /// Typically, this is the join point for a rendezvous circuit for
84
    /// communicating with or as an onion service.
85
    VirtualHop {},
86

            
87
    /// Return
88
    KnownRelay {
89
        /// A set of IDs for this Tor relay.
90
        ///
91
        /// Each ID represents a long-term public key used to identify the relay.
92
        /// Deprecated ID types are not included, unless they were specifically requested.
93
        ids: RelayIds,
94

            
95
        /// A list of the relay's addresses.
96
        addrs: Vec<SocketAddr>,
97
    },
98
}
99

            
100
/// Serializable container of relay identities.
101
///
102
/// Differs from [`tor_linkspec::RelayIds`] in is serialize behavior;
103
/// See <https://gitlab.torproject.org/tpo/core/arti/-/issues/2477>.
104
///
105
/// Every relay will have at least one ID.  On the current Tor network
106
/// as of April 2026, every relay has an Ed25519 ID.
107
/// (This may not always be the case in the future;
108
/// for example, if we migrate to ML-DSA keys,
109
/// we may eventually retire Ed25519 IDs.)
110
#[derive(Clone, Debug, serde::Serialize)]
111
pub struct RelayIds {
112
    /// Copy of the ed25519 id from the underlying ChanTarget.
113
    #[serde(rename = "ed25519", skip_serializing_if = "Option::is_none")]
114
    ed_identity: Option<pk::ed25519::Ed25519Identity>,
115

            
116
    /// Copy of the rsa id from the underlying ChanTarget.
117
    ///
118
    /// This is a deprecated ID type.
119
    #[serde(rename = "rsa", skip_serializing_if = "Option::is_none")]
120
    rsa_identity: Option<pk::rsa::RsaIdentity>,
121
}
122

            
123
/// A description of a tunnel's path.
124
///
125
/// Note that tunnels are potentially made of multiple circuits, even though
126
/// Arti (as of April 2026) does not yet build Conflux tunnels.
127
/// Therefore, users should make sure to handle multi-path tunnels here.
128
#[derive(serde::Serialize, Clone, Debug)]
129
pub struct PathDescription {
130
    /// The entries in a given tunnel's path(s).
131
    ///
132
    /// Within each path, entries are ordered from first (closest to the client)
133
    /// to last (farthest from the client).
134
    ///
135
    /// Since tunnels can have multiple paths, each path is identified with a string.
136
    /// The actual content of these strings is not documented,
137
    /// but the string identifying each tunnel is stable.
138
    ///
139
    /// (That is, if you query a tunnel's path description twice,
140
    /// each path will have the same identifying string both times.
141
    /// If you get a new identifying string,
142
    /// it represents a path that was previously not part of the tunnel.)
143
    path: HashMap<String, Vec<PathEntry>>,
144
}
145

            
146
impl PathEntry {
147
    /// Construct a PathEntry from a PathEntry returned by a circuit.
148
    fn from_client_entry(
149
        detail: &crate::client::circuit::PathEntry,
150
        command: &DescribePath,
151
    ) -> Self {
152
        let Some(owned_chan_target) = detail.as_chan_target() else {
153
            return PathEntry::VirtualHop {};
154
        };
155

            
156
        let ids = tor_linkspec::RelayIds::from_relay_ids(owned_chan_target);
157
        let ids = RelayIds {
158
            ed_identity: ids.ed_identity().cloned(),
159
            rsa_identity: if command.include_deprecated_ids {
160
                ids.rsa_identity().cloned()
161
            } else {
162
                None
163
            },
164
        };
165
        let addrs = owned_chan_target.addrs().collect();
166
        PathEntry::KnownRelay { ids, addrs }
167
    }
168
}
169

            
170
/// Helper: Return the [`ClientTunnel`] for a [`ClientDataStreamCtrl`],
171
/// or an RPC error if the stream isn't attached to a tunnel.
172
fn client_stream_tunnel(stream: &ClientDataStreamCtrl) -> Result<Arc<ClientTunnel>, rpc::RpcError> {
173
    stream.tunnel().ok_or_else(|| {
174
        rpc::RpcError::new(
175
            "Stream was not attached to a tunnel".to_string(),
176
            rpc::RpcErrorKind::RequestError,
177
        )
178
    })
179
}
180

            
181
/// Helper: Return a [`PathDescription`] for a [`ClientTunnel`]
182
fn tunnel_path(tunnel: &ClientTunnel, method: &DescribePath) -> PathDescription {
183
    let path = tunnel
184
        .tagged_paths()
185
        .into_iter()
186
        .map(|(id, path)| {
187
            let id = id.display_chan_circ().to_string();
188
            let path = path
189
                .iter()
190
                .map(|hop| PathEntry::from_client_entry(hop, method))
191
                .collect();
192
            (id, path)
193
        })
194
        .collect();
195
    PathDescription { path }
196
}
197

            
198
/// Implementation function: implements GetTunnel on ClientDataStreamCtrl.
199
async fn client_data_stream_ctrl_get_tunnel(
200
    target: Arc<ClientDataStreamCtrl>,
201
    _method: Box<GetTunnel>,
202
    ctx: Arc<dyn rpc::Context>,
203
) -> Result<rpc::SingleIdResponse, rpc::RpcError> {
204
    let tunnel: Arc<dyn rpc::Object> = client_stream_tunnel(&target)? as _;
205
    let id = ctx.register_owned(tunnel);
206
    Ok(SingleIdResponse::from(id))
207
}
208

            
209
/// Implementation function: implements DescribePath on a ClientTunnel.
210
async fn client_tunnel_describe_path(
211
    target: Arc<ClientTunnel>,
212
    method: Box<DescribePath>,
213
    _ctx: Arc<dyn rpc::Context>,
214
) -> Result<PathDescription, rpc::RpcError> {
215
    Ok(tunnel_path(&target, &method))
216
}
217

            
218
/// Implementation function: implements DescribePath on a ClientDataStreamCtrl.
219
async fn client_data_stream_ctrl_describe_path(
220
    target: Arc<ClientDataStreamCtrl>,
221
    method: Box<DescribePath>,
222
    _ctx: Arc<dyn rpc::Context>,
223
) -> Result<PathDescription, rpc::RpcError> {
224
    let tunnel = client_stream_tunnel(&target)?;
225
    Ok(tunnel_path(&tunnel, &method))
226
}
227

            
228
rpc::static_rpc_invoke_fn! {
229
    client_data_stream_ctrl_get_tunnel;
230
    client_tunnel_describe_path;
231
    client_data_stream_ctrl_describe_path;
232
}