Skip to main content

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