1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
//! Declare RPC functionality on for the `arti-client` crate.

use async_trait::async_trait;
use derive_deftly::Deftly;
use dyn_clone::DynClone;
use futures::{SinkExt as _, StreamExt as _};
use serde::{Deserialize, Serialize};
use std::{net::IpAddr, sync::Arc};
use tor_proto::stream::DataStream;

use tor_rpcbase as rpc;
use tor_rtcompat::Runtime;

use crate::{StreamPrefs, TorAddr, TorClient};

impl<R: Runtime> TorClient<R> {
    /// Ensure that every RPC method is registered for this instantiation of TorClient.
    ///
    /// We can't use [`rpc::static_rpc_invoke_fn`] for these, since TorClient is
    /// parameterized.
    pub fn rpc_methods() -> Vec<rpc::dispatch::InvokerEnt> {
        rpc::invoker_ent_list![
            get_client_status::<R>,
            watch_client_status::<R>,
            isolated_client::<R>,
        ]
    }
}

/// RPC method: Return the current ClientStatusInfo.
#[derive(Deftly, Debug, Serialize, Deserialize)]
#[derive_deftly(rpc::DynMethod)]
#[deftly(rpc(method_name = "arti:get_client_status"))]
struct GetClientStatus {}

impl rpc::Method for GetClientStatus {
    type Output = ClientStatusInfo;
    type Update = rpc::NoUpdates;
    type Error = rpc::RpcError;
}

/// RPC method: Run forever, delivering an updated view of the ClientStatusInfo whenever it changes.
///
/// (This method can return updates that have no visible changes.)
#[derive(Deftly, Debug, Serialize, Deserialize)]
#[derive_deftly(rpc::DynMethod)]
#[deftly(rpc(method_name = "arti:watch_client_status"))]
struct WatchClientStatus {}

impl rpc::Method for WatchClientStatus {
    type Output = rpc::Nil;
    type Update = ClientStatusInfo;
    type Error = rpc::RpcError;
}

/// RPC result: The reported status of a TorClient.
#[derive(Serialize, Deserialize)]
struct ClientStatusInfo {
    /// True if the client is ready for traffic.
    ready: bool,
    /// Approximate estimate of how close the client is to being ready for traffic.
    fraction: f32,
    /// If present, a description of possible problem(s) that may be stopping
    /// the client from using the Tor network.
    blocked: Option<String>,
}

impl From<crate::status::BootstrapStatus> for ClientStatusInfo {
    fn from(s: crate::status::BootstrapStatus) -> Self {
        let ready = s.ready_for_traffic();
        let fraction = s.as_frac();
        let blocked = s.blocked().map(|b| b.to_string());
        Self {
            ready,
            fraction,
            blocked,
        }
    }
}

// NOTE: These functions could be defined as methods on TorClient<R>.
// I'm defining them like this to make it more clear that they are never
// invoked as client.method(), but only via the RPC system.
// We can revisit this later if we want.

// TODO RPC: Once we have one or two more get/watch combinations,
// we should look into some facility for automatically declaring them,
// so that their behavior stays uniform.
//
// See https://gitlab.torproject.org/tpo/core/arti/-/issues/1384#note_3023659

/// Invocable function to run [`GetClientStatus`] on a [`TorClient`].
async fn get_client_status<R: Runtime>(
    client: Arc<TorClient<R>>,
    _method: Box<GetClientStatus>,
    _ctx: Box<dyn rpc::Context>,
) -> Result<ClientStatusInfo, rpc::RpcError> {
    Ok(client.bootstrap_status().into())
}

/// Invocable function to run [`WatchClientStatus`] on a [`TorClient`].
async fn watch_client_status<R: Runtime>(
    client: Arc<TorClient<R>>,
    _method: Box<WatchClientStatus>,
    _ctx: Box<dyn rpc::Context>,
    mut updates: rpc::UpdateSink<ClientStatusInfo>,
) -> Result<rpc::Nil, rpc::RpcError> {
    let mut events = client.bootstrap_events();

    // Send the _current_ status, no matter what.
    // (We do this after constructing er)
    updates.send(client.bootstrap_status().into()).await?;

    // Send additional updates whenever the status changes.
    while let Some(status) = events.next().await {
        updates.send(status.into()).await?;
    }

    // This can only happen if the client exits.
    Ok(rpc::NIL)
}

/// RPC method: Return an owned ID for a new isolated client instance.
#[derive(Deftly, Debug, Serialize, Deserialize)]
#[derive_deftly(rpc::DynMethod)]
#[deftly(rpc(method_name = "arti:isolated_client"))]
#[non_exhaustive]
pub struct IsolatedClient {}

impl rpc::Method for IsolatedClient {
    type Output = rpc::SingletonId;
    type Update = rpc::NoUpdates;
    type Error = rpc::RpcError;
}

/// RPC method implementation: return a new isolated client based on a given client.
async fn isolated_client<R: Runtime>(
    client: Arc<TorClient<R>>,
    _method: Box<IsolatedClient>,
    ctx: Box<dyn rpc::Context>,
) -> Result<rpc::SingletonId, rpc::RpcError> {
    let new_client = Arc::new(client.isolated_client());
    let client_id = ctx.register_owned(new_client);
    Ok(rpc::SingletonId::from(client_id))
}

/// Type-erased error returned by ClientConnectionTarget.
//
// TODO RPC: It would be handy if this implemented HasErrorHint, but HasErrorHint is sealed.
// Perhaps we could go and solve our problem by implementing HasErrorHint on dyn StdError?
pub trait ClientConnectionError:
    std::error::Error + tor_error::HasKind + DynClone + Send + Sync + seal::Sealed
{
}
impl<E> seal::Sealed for E where E: std::error::Error + tor_error::HasKind + DynClone + Send + Sync {}
impl<E> ClientConnectionError for E where
    E: std::error::Error + tor_error::HasKind + DynClone + Send + Sync + seal::Sealed
{
}
impl std::error::Error for Box<dyn ClientConnectionError> {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        self.as_ref().source()
    }
}
dyn_clone::clone_trait_object!(ClientConnectionError);

/// module to seal the ClientConnectionError trait.
mod seal {
    /// hidden trait to seal the ClientConnectionError trait.
    #[allow(unreachable_pub)]
    pub trait Sealed {}
}

/// Type alias for a Result return by ClientConnectionTarget
pub type ClientConnectionResult<T> = Result<T, Box<dyn ClientConnectionError>>;

/// An RPC-visible object that can be used as the target of SOCKS operations,
/// or other application-level connection attempts.
///
/// Only the RPC subsystem should use this type.
///
/// Semantically, you can consider this trait as a collection of three Methods
/// that certain RPC objects implement.  We aren't implementing this directly
/// as rpc::Methods because they cannot (currently) return non-Serialize types.
//
// TODO RPC: Conceivably, we would like to apply this trait to types in lower-level crates: for
// example, we could put it onto ClientCirc, and let the application launch streams on a circuit
// directly.  But if we did that, we wouldn't be able to downcast an ClientCirc from Arc<dyn Object>
// to this trait. Perhaps we need a clever solution.
//
// TODO RPC: This trait, along with ClientConnection{Error,Result},  have names that are just too
// long.
//
// TODO RPC: We might like to replace this with a special kind of RPC method;
// see #1403.
#[async_trait]
pub trait ClientConnectionTarget: Send + Sync {
    /// As [`TorClient::connect_with_prefs`].
    async fn connect_with_prefs(
        &self,
        target: &TorAddr,
        prefs: &StreamPrefs,
    ) -> ClientConnectionResult<DataStream>;

    /// As [`TorClient::resolve_with_prefs`].
    async fn resolve_with_prefs(
        &self,
        hostname: &str,
        prefs: &StreamPrefs,
    ) -> ClientConnectionResult<Vec<IpAddr>>;

    /// As [`TorClient::resolve_ptr_with_prefs`].
    async fn resolve_ptr_with_prefs(
        &self,
        addr: IpAddr,
        prefs: &StreamPrefs,
    ) -> ClientConnectionResult<Vec<String>>;
}

#[async_trait]
impl<R: Runtime> ClientConnectionTarget for TorClient<R> {
    async fn connect_with_prefs(
        &self,
        target: &TorAddr,
        prefs: &StreamPrefs,
    ) -> ClientConnectionResult<DataStream> {
        TorClient::connect_with_prefs(self, target, prefs)
            .await
            .map_err(|e| Box::new(e) as _)
    }
    async fn resolve_with_prefs(
        &self,
        hostname: &str,
        prefs: &StreamPrefs,
    ) -> ClientConnectionResult<Vec<IpAddr>> {
        TorClient::resolve_with_prefs(self, hostname, prefs)
            .await
            .map_err(|e| Box::new(e) as _)
    }
    async fn resolve_ptr_with_prefs(
        &self,
        addr: IpAddr,
        prefs: &StreamPrefs,
    ) -> ClientConnectionResult<Vec<String>> {
        TorClient::resolve_ptr_with_prefs(self, addr, prefs)
            .await
            .map_err(|e| Box::new(e) as _)
    }
}