tor_rpcbase/obj.rs
1//! Object type for our RPC system.
2
3pub(crate) mod cast;
4
5use std::sync::Arc;
6
7use derive_deftly::define_derive_deftly;
8use downcast_rs::DowncastSync;
9use serde::{Deserialize, Serialize};
10
11use self::cast::CastTable;
12
13/// An object in our RPC system to which methods can be addressed.
14///
15/// You shouldn't implement this trait yourself; instead, use the
16/// [`derive_deftly(Object)`].
17///
18/// See the documentation for [`derive_deftly(Object)`]
19/// for examples of how to declare and
20/// downcast `Object`s.
21///
22/// [`derive_deftly(Object)`]: crate::templates::derive_deftly_template_Object
23pub trait Object: DowncastSync + Send + Sync + 'static {
24 /// Return true if this object should be given an identifier that allows it
25 /// to be used outside of the session that generated it.
26 ///
27 /// Currently, the only use for such IDs in arti is identifying stream
28 /// contexts in when opening a SOCKS connection: When an application opens a
29 /// stream, it needs to declare what RPC context (like a `TorClient`) it's
30 /// using, which requires that some identifier for that context exist
31 /// outside of the RPC session that owns it.
32 fn expose_outside_of_session(&self) -> bool {
33 false
34 }
35
36 /// Return a [`CastTable`] that can be used to downcast a `dyn Object` of
37 /// this type into various kinds of `dyn Trait` references.
38 ///
39 /// The default implementation of this method declares that the `Object`
40 /// can't be downcast into any traits.
41 ///
42 /// You should not implement this method yourself; instead use
43 /// [`derive_deftly(Object)`](crate::templates::derive_deftly_template_Object).
44 fn get_cast_table(&self) -> &CastTable {
45 &cast::EMPTY_CAST_TABLE
46 }
47
48 /// Optionally, return a delegation target for this `Object``.
49 ///
50 /// If method lookup fails on this object, then the `delegate`
51 fn delegate(&self) -> Option<Arc<dyn Object>> {
52 None
53 }
54}
55downcast_rs::impl_downcast!(sync Object);
56
57/// An identifier for an Object within the context of a Session.
58///
59/// These are opaque from the client's perspective.
60#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize)]
61#[serde(transparent)]
62pub struct ObjectId(
63 // (We use Box<str> to save a word here, since these don't have to be
64 // mutable ever.)
65 Box<str>,
66);
67
68impl AsRef<str> for ObjectId {
69 fn as_ref(&self) -> &str {
70 self.0.as_ref()
71 }
72}
73
74impl<T> From<T> for ObjectId
75where
76 T: Into<Box<str>>,
77{
78 fn from(value: T) -> Self {
79 Self(value.into())
80 }
81}
82
83/// Extension trait for `Arc<dyn Object>` to support convenient
84/// downcasting to `dyn Trait`.
85///
86/// You don't need to use this for downcasting to an object's concrete
87/// type; for that, use [`downcast_rs::DowncastSync`].
88///
89/// # Examples
90///
91/// ```
92/// use tor_rpcbase::{Object, ObjectArcExt, templates::*};
93/// use derive_deftly::Deftly;
94/// use std::sync::Arc;
95///
96/// #[derive(Deftly)]
97/// #[derive_deftly(Object)]
98/// #[deftly(rpc(downcastable_to = "HasFeet"))]
99/// pub struct Frog {}
100/// pub trait HasFeet {
101/// fn num_feet(&self) -> usize;
102/// }
103/// impl HasFeet for Frog {
104/// fn num_feet(&self) -> usize { 4 }
105/// }
106///
107/// /// If `obj` is a HasFeet, return how many feet it has.
108/// /// Otherwise, return 0.
109/// fn check_feet(obj: Arc<dyn Object>) -> usize {
110/// let maybe_has_feet: Option<&dyn HasFeet> = obj.cast_to_trait();
111/// match maybe_has_feet {
112/// Some(foot_haver) => foot_haver.num_feet(),
113/// None => 0,
114/// }
115/// }
116///
117/// assert_eq!(check_feet(Arc::new(Frog{})), 4);
118/// ```
119pub trait ObjectArcExt {
120 /// Try to cast this `Arc<dyn Object>` to a `T`. On success, return a reference to
121 /// T; on failure, return None.
122 fn cast_to_trait<T: ?Sized + 'static>(&self) -> Option<&T>;
123
124 /// Try to cast this `Arc<dyn Object>` to an `Arc<T>`.
125 fn cast_to_arc_trait<T: ?Sized + 'static>(self) -> Result<Arc<T>, Arc<dyn Object>>;
126}
127
128impl dyn Object {
129 /// Try to cast this `Object` to a `T`. On success, return a reference to
130 /// T; on failure, return None.
131 ///
132 /// This method is only for casting to `&dyn Trait`;
133 /// see [`ObjectArcExt`] for limitations.
134 pub fn cast_to_trait<T: ?Sized + 'static>(&self) -> Option<&T> {
135 let table = self.get_cast_table();
136 table.cast_object_to(self)
137 }
138}
139
140impl ObjectArcExt for Arc<dyn Object> {
141 fn cast_to_trait<T: ?Sized + 'static>(&self) -> Option<&T> {
142 let obj: &dyn Object = self.as_ref();
143 obj.cast_to_trait()
144 }
145 fn cast_to_arc_trait<T: ?Sized + 'static>(self) -> Result<Arc<T>, Arc<dyn Object>> {
146 let table = self.get_cast_table();
147 table.cast_object_to_arc(self.clone())
148 }
149}
150
151define_derive_deftly! {
152/// Allow a type to participate as an Object in the RPC system.
153///
154/// This template implements `Object` for the
155/// target type, and can be used to cause objects to participate in the trait
156/// downcasting system.
157///
158/// # Examples
159///
160/// ## Simple case, just implements `Object`.
161///
162/// ```
163/// use tor_rpcbase::{self as rpc, templates::*};
164/// use derive_deftly::Deftly;
165///
166/// #[derive(Default, Deftly)]
167/// #[derive_deftly(Object)]
168/// struct Houseplant {
169/// oxygen_per_sec: f64,
170/// benign_neglect: u8
171/// }
172///
173/// // You can downcast an Object to a concrete type.
174/// use downcast_rs::DowncastSync;
175/// use std::sync::Arc;
176/// let plant_obj: Arc<dyn rpc::Object> = Arc::new(Houseplant::default());
177/// let as_plant: Arc<Houseplant> = plant_obj.downcast_arc().ok().unwrap();
178/// ```
179///
180/// ## With trait downcasting
181///
182/// By default, you can use [`downcast_rs`] to downcast a `dyn Object` to its
183/// concrete type. If you also need to be able to downcast a `dyn Object` to a given
184/// trait that it implements, you can use the `downcastable_to` attributes for `Object` to have
185/// it participate in trait downcasting:
186///
187/// ```
188/// use tor_rpcbase::{self as rpc, templates::*};
189/// use derive_deftly::Deftly;
190///
191/// #[derive(Deftly)]
192/// #[derive_deftly(Object)]
193/// #[deftly(rpc(downcastable_to = "Gizmo, Doodad"))]
194/// struct Frobnitz {}
195///
196/// trait Gizmo {}
197/// trait Doodad {}
198/// impl Gizmo for Frobnitz {}
199/// impl Doodad for Frobnitz {}
200///
201/// use std::sync::Arc;
202/// use rpc::ObjectArcExt; // for the cast_to method.
203/// let frob_obj: Arc<dyn rpc::Object> = Arc::new(Frobnitz {});
204/// let gizmo: &dyn Gizmo = frob_obj.cast_to_trait().unwrap();
205/// let doodad: &dyn Doodad = frob_obj.cast_to_trait().unwrap();
206/// ```
207///
208/// ## With generic objects
209///
210/// Right now, a generic object can't participate in our method lookup system,
211/// but it _can_ participate in trait downcasting. We'll try to remove this
212/// limitation in the future.
213///
214/// ```
215/// use tor_rpcbase::{self as rpc, templates::*};
216/// use derive_deftly::Deftly;
217///
218/// #[derive(Deftly)]
219/// #[derive_deftly(Object)]
220/// #[deftly(rpc(downcastable_to = "ExampleTrait"))]
221/// struct Generic<T,U> where T:Clone, U:PartialEq {
222/// t: T,
223/// u: U,
224/// }
225///
226/// trait ExampleTrait {}
227/// impl<T:Clone,U:PartialEq> ExampleTrait for Generic<T,U> {}
228///
229/// use std::sync::Arc;
230/// use rpc::ObjectArcExt; // for the cast_to method.
231/// let obj: Arc<dyn rpc::Object> = Arc::new(Generic { t: 42_u8, u: 42_u8 });
232/// let tr: &dyn ExampleTrait = obj.cast_to_trait().unwrap();
233/// ```
234///
235/// ## Making an object "exposed outside of the session"
236///
237/// You can flag any kind of Object so that its identifiers will be exported
238/// outside of the local RPC session. (Arti uses this for Objects whose
239/// ObjectId needs to be used as a SOCKS identifier.) To do so,
240/// use the `expose_outside_session` attribute:
241///
242/// ```
243/// use tor_rpcbase::{self as rpc, templates::*};
244/// use derive_deftly::Deftly;
245///
246/// #[derive(Deftly)]
247/// #[derive_deftly(Object)]
248/// #[deftly(rpc(expose_outside_of_session))]
249/// struct Visible {}
250/// ```
251///
252/// ## Delegation
253///
254/// You can give an Object the ability to delegate
255/// method invocations to another object it contains.
256/// The inner object must be an `Arc`.
257/// To do so, use the `delegate_with` attribute.
258/// The attribute must contain an expression of type
259/// `FnOnce(&Self) -> Option(Arc<T>)`, where T implements Object.
260///
261/// ```
262/// use tor_rpcbase::{self as rpc, templates::*};
263/// use derive_deftly::Deftly;
264/// use std::sync::Arc;
265///
266/// #[derive(Deftly)]
267/// #[derive_deftly(Object)]
268/// struct Inner {}
269///
270/// #[derive(Deftly)]
271/// #[derive_deftly(Object)]
272/// #[deftly(rpc(
273/// delegate_with="|this: &Self| Some(this.inner.clone())",
274/// delegate_type="Inner"
275/// ))]
276/// struct Outer {
277/// inner: Arc<Inner>,
278/// }
279/// ```
280///
281 export Object expect items:
282
283
284 impl<$tgens> $ttype where
285 // We need this restriction in case there are generics
286 // that might not impl these traits.
287 $ttype: Send + Sync + 'static,
288 $twheres
289 {
290 /// Construct a new `CastTable` for this type.
291 ///
292 /// This is a function so that we can call it multiple times as
293 /// needed if the type is generic.
294 ///
295 /// Don't invoke this yourself; instead use `decl_object!`.
296 #[doc(hidden)]
297 fn make_cast_table() -> $crate::CastTable {
298 ${if tmeta(rpc(downcastable_to)) {
299 $crate::cast_table_deftness_helper!{
300 // TODO ideally we would support multiple downcastable_to rather
301 // than a single list, and use `as ty`
302 ${tmeta(rpc(downcastable_to)) as token_stream}
303 }
304 } else {
305 $crate::CastTable::default()
306 }}
307 }
308 }
309
310 ${if tmeta(rpc(delegate_type)) {
311 $crate::register_delegation_note!(
312 $ttype,
313 ${tmeta(rpc(delegate_type )) as ty}
314 );
315 }}
316
317 ${if tmeta(rpc(delegate_type)) {
318 #[doc = "Delegates to [`"]
319 #[doc = ${tmeta(rpc(delegate_type)) as str}]
320 #[doc = "`]"]
321 }}
322 impl<$tgens> $crate::Object for $ttype where
323 // We need this restriction in case there are generics
324 // that might not impl these traits.
325 $ttype: Send + Sync + 'static,
326 $twheres
327 {
328 ${if tmeta(rpc(expose_outside_of_session)) {
329 fn expose_outside_of_session(&self) -> bool {
330 true
331 }
332 }}
333
334 ${if tmeta(rpc(delegate_with)) {
335 fn delegate(&self) -> Option<Arc<dyn $crate::Object>> {
336 let r: Option<Arc<${tmeta(rpc(delegate_type)) as ty}>> = (${tmeta(rpc(delegate_with)) as expr})(self);
337
338 r.map(|v| v as Arc<dyn $crate::Object>)
339 }
340 }}
341
342 fn get_cast_table(&self) -> &$crate::CastTable {
343 ${if tgens {
344 // For generic types, we have a potentially unbounded number
345 // of CastTables: one for each instantiation of the type.
346 // Therefore we keep a mutable add-only HashMap of CastTables.
347
348 use $crate::once_cell::sync::Lazy;
349 use std::sync::RwLock;
350 use std::collections::HashMap;
351 use std::any::TypeId;
352 // Map from concrete type to CastTable.
353 //
354 // Note that we use `&'static CastTable` here, not
355 // `Box<CastTable>`: If we used Box<>, the borrow checker would
356 // worry that our `CastTable`s might get freed after we returned
357 // a reference to them. Using `&'static` guarantees that the CastTable
358 // references are safe to return.
359 //
360 // In order to get a `&'static`, we need to use Box::leak().
361 // That's fine, since we only create one CastTable per
362 // instantiation of the type.
363 static TABLES: Lazy<RwLock<HashMap<TypeId, &'static $crate::CastTable>>> =
364 Lazy::new(|| RwLock::new(HashMap::new()));
365 {
366 let tables_r = TABLES.read().expect("poisoned lock");
367 if let Some(table) = tables_r.get(&TypeId::of::<Self>()) {
368 // Fast case: we already had a CastTable for this instantiation.
369 table
370 } else {
371 // We didn't find a CastTable.
372 drop(tables_r); // prevent deadlock.
373 TABLES
374 .write()
375 .expect("poisoned lock")
376 .entry(TypeId::of::<Self>())
377 // We use `or_insert_with` here to avoid a race
378 // condition: we only want to call make_cast_table if
379 // one didn't already exist.
380 .or_insert_with(|| Box::leak(Box::new(Self::make_cast_table())))
381 }
382 }
383 } else {
384 // For non-generic types, we only ever have a single CastTable,
385 // so we can just construct it once and return it.
386 use $crate::once_cell::sync::Lazy;
387 static TABLE: Lazy<$crate::CastTable> = Lazy::new(|| $ttype::make_cast_table());
388 &TABLE
389 }}
390 }
391 }
392}
393pub use derive_deftly_template_Object;
394
395#[cfg(test)]
396mod test {
397 // @@ begin test lint list maintained by maint/add_warning @@
398 #![allow(clippy::bool_assert_comparison)]
399 #![allow(clippy::clone_on_copy)]
400 #![allow(clippy::dbg_macro)]
401 #![allow(clippy::mixed_attributes_style)]
402 #![allow(clippy::print_stderr)]
403 #![allow(clippy::print_stdout)]
404 #![allow(clippy::single_char_pattern)]
405 #![allow(clippy::unwrap_used)]
406 #![allow(clippy::unchecked_duration_subtraction)]
407 #![allow(clippy::useless_vec)]
408 #![allow(clippy::needless_pass_by_value)]
409 //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
410
411 use super::*;
412 use derive_deftly::Deftly;
413
414 #[derive(Deftly)]
415 #[derive_deftly(Object)]
416 #[deftly(rpc(downcastable_to = "HasWheels"))]
417 struct Bicycle {}
418 trait HasWheels {
419 fn num_wheels(&self) -> usize;
420 }
421 impl HasWheels for Bicycle {
422 fn num_wheels(&self) -> usize {
423 2
424 }
425 }
426
427 #[derive(Deftly)]
428 #[derive_deftly(Object)]
429 struct Opossum {}
430
431 #[test]
432 fn standard_cast() {
433 let bike = Bicycle {};
434 let erased_bike: &dyn Object = &bike;
435 let has_wheels: &dyn HasWheels = erased_bike.cast_to_trait().unwrap();
436 assert_eq!(has_wheels.num_wheels(), 2);
437
438 let pogo = Opossum {};
439 let erased_pogo: &dyn Object = &pogo;
440 let has_wheels: Option<&dyn HasWheels> = erased_pogo.cast_to_trait();
441 assert!(has_wheels.is_none());
442 }
443
444 #[derive(Deftly)]
445 #[derive_deftly(Object)]
446 #[deftly(rpc(downcastable_to = "HasWheels"))]
447 struct Crowd<T: HasWheels + Send + Sync + 'static> {
448 members: Vec<T>,
449 }
450 impl<T: HasWheels + Send + Sync> HasWheels for Crowd<T> {
451 fn num_wheels(&self) -> usize {
452 self.members.iter().map(T::num_wheels).sum()
453 }
454 }
455
456 #[test]
457 fn generic_cast() {
458 let bikes = Crowd {
459 members: vec![Bicycle {}, Bicycle {}],
460 };
461 let erased_bikes: &dyn Object = &bikes;
462 let has_wheels: &dyn HasWheels = erased_bikes.cast_to_trait().unwrap();
463 assert_eq!(has_wheels.num_wheels(), 4);
464
465 let arc_bikes = Arc::new(bikes);
466 let erased_arc_bytes: Arc<dyn Object> = arc_bikes.clone();
467 let arc_has_wheels: Arc<dyn HasWheels> =
468 erased_arc_bytes.clone().cast_to_arc_trait().ok().unwrap();
469 assert_eq!(arc_has_wheels.num_wheels(), 4);
470
471 let ref_has_wheels: &dyn HasWheels = erased_arc_bytes.cast_to_trait().unwrap();
472 assert_eq!(ref_has_wheels.num_wheels(), 4);
473
474 trait SomethingElse {}
475 let arc_something_else: Result<Arc<dyn SomethingElse>, _> =
476 erased_arc_bytes.clone().cast_to_arc_trait();
477 let err_arc = arc_something_else.err().unwrap();
478 assert!(Arc::ptr_eq(&err_arc, &erased_arc_bytes));
479 }
480
481 #[derive(Deftly)]
482 #[derive_deftly(Object)]
483 #[deftly(rpc(delegate_with = "|cage: &Self| Some(cage.possum.clone())"))]
484 #[deftly(rpc(delegate_type = "Opossum"))]
485 struct PossumCage {
486 possum: Arc<Opossum>,
487 }
488}