tor_rpcbase/
method.rs

1//! Method type for the RPC system.
2
3use std::{
4    any,
5    collections::{HashMap, HashSet},
6    sync::Arc,
7};
8
9use derive_deftly::define_derive_deftly;
10use downcast_rs::Downcast;
11use once_cell::sync::Lazy;
12
13/// The parameters and method name associated with a given Request.
14///
15/// Use [`derive_deftly(DynMethod)`](derive_deftly_template_DynMethod)
16/// for a template to declare one of these.
17///
18/// To be invoked from RPC, a method must additionally implement [`DeserMethod`].
19///
20/// ## Note
21///
22/// As a consequence of this trait being public, any crate can define a method
23/// on an object, even if the method and object are defined in another crate:
24/// This should be okay, since:
25///
26/// * the crate should only have access to the public Rust methods of the object,
27///   which is presumably safe to call.
28/// * if you are linking a crate, you are already trusting that crate.
29pub trait DynMethod: std::fmt::Debug + Send + Downcast {
30    /// Try to invoke a method while bypassing the regular RPC method dispatch system.
31    ///
32    /// For nearly all `DynMethod` types, this method will return
33    /// `Err(InvokeError::NoDispatchBypass)`, indicating that the caller should fall through
34    /// and use the regular method dispatch system.
35    ///
36    /// This mechanism is suitable for cases like "rpc:release"
37    /// where the correct behavior for the method
38    /// does not depend at all on the _type_ of the object it's being invoked on,
39    /// but instead the method is meant to manipulate the object reference itself.
40    fn invoke_without_dispatch(
41        &self,
42        ctx: Arc<dyn crate::Context>,
43        obj_id: &ObjectId,
44    ) -> Result<crate::dispatch::RpcResultFuture, crate::InvokeError> {
45        let _ = ctx;
46        let _ = obj_id;
47        Err(crate::InvokeError::NoDispatchBypass)
48    }
49
50    /// Return true if this method is safe to cancel.
51    ///
52    /// The default implementation returns true: all RPC methods should be cancel-safe and
53    /// and cancellable.
54    ///
55    /// In Arti currently, only the "cancel" method itself is marked as uncancellable,
56    /// to avoid deadlocks.
57    fn is_cancellable(&self) -> bool {
58        true
59    }
60}
61downcast_rs::impl_downcast!(DynMethod);
62
63/// A DynMethod that can be deserialized.
64///
65/// We use [`typetag`] here so that we define `Method`s in other crates.
66///
67/// Use [`derive_deftly(DynMethod)`](derive_deftly_template_DynMethod)
68/// for a template to declare one of these.
69#[typetag::deserialize(tag = "method", content = "params")]
70pub trait DeserMethod: DynMethod {
71    /// Up-cast to a `Box<dyn DynMethod>`.
72    fn upcast_box(self: Box<Self>) -> Box<dyn DynMethod>;
73}
74
75/// A typed method, used to ensure that all implementations of a method have the
76/// same success and updates types.
77///
78/// Prefer to implement this trait or [`RpcMethod`], rather than `DynMethod` or `DeserMethod`.
79/// (Those traits represent a type-erased method, with statically-unknown `Output` and
80/// `Update` types.)
81///
82/// All Methods can be invoked via `DispatchTable::invoke_special`.
83/// To be invoked from the RPC system, a methods associated `Output` and `Update` types
84/// must additionally implement `Serialize`, and its `Error` type must implement
85/// `Into<RpcError>`
86pub trait Method: DynMethod {
87    /// A type returned by this method.
88    type Output: Send + 'static;
89    /// A type sent by this method on updates.
90    ///
91    /// If this method will never send updates, use the uninhabited
92    /// [`NoUpdates`] type.
93    type Update: Send + 'static;
94}
95
96/// A method that can be invoked from the RPC system.
97///
98/// Every RpcMethod automatically implements `Method`.
99pub trait RpcMethod: DeserMethod {
100    /// A type returned by this method _on success_.
101    ///
102    /// (The actual result type from the function implementing this method is `Result<Output,E>`,
103    /// where E implements `RpcError`.)
104    type Output: Send + serde::Serialize + 'static;
105
106    /// A type sent by this method on updates.
107    ///
108    /// If this method will never send updates, use the uninhabited
109    /// [`NoUpdates`] type.
110    type Update: Send + serde::Serialize + 'static;
111}
112
113impl<T: RpcMethod> Method for T {
114    type Output = Result<<T as RpcMethod>::Output, crate::RpcError>;
115    type Update = <T as RpcMethod>::Update;
116}
117
118/// An uninhabited type, used to indicate that a given method will never send
119/// updates.
120#[derive(serde::Serialize)]
121#[allow(clippy::exhaustive_enums)]
122pub enum NoUpdates {}
123
124/// A method we're registering.
125///
126/// This struct's methods are public so it can be constructed from
127/// `decl_method!`.
128///
129/// If you construct it yourself, you'll be in trouble.  But you already knew
130/// that, since you're looking at a `doc(hidden)` thing.
131#[doc(hidden)]
132#[allow(clippy::exhaustive_structs)]
133pub struct MethodInfo_ {
134    /// The name of the method.
135    pub method_name: &'static str,
136    /// A function returning the TypeId for this method's underlying type.
137    ///
138    /// (This needs to be a fn since TypeId::of isn't `const` yet.)
139    pub typeid: fn() -> any::TypeId,
140    /// A function returning the name for this method's output type.
141    pub output_name: fn() -> &'static str,
142    /// A function returning the name for this method's update type.
143    pub update_name: fn() -> &'static str,
144}
145
146inventory::collect!(MethodInfo_);
147
148define_derive_deftly! {
149/// Declare that one or more space-separated types should be considered
150/// as dynamically dispatchable RPC methods.
151///
152/// # Example
153///
154/// ```
155/// use tor_rpcbase::{self as rpc, templates::*};
156/// use derive_deftly::Deftly;
157///
158/// #[derive(Debug, serde::Deserialize, Deftly)]
159/// #[derive_deftly(rpc::DynMethod)]
160/// #[deftly(rpc(method_name = "x-example:castigate"))]
161/// struct Castigate {
162///    severity: f64,
163///    offenses: Vec<String>,
164///    accomplice: Option<rpc::ObjectId>,
165/// }
166///
167/// impl rpc::RpcMethod for Castigate {
168///     type Output = String;
169///     type Update = rpc::NoUpdates;
170/// }
171/// ```
172    export DynMethod:
173    const _: () = {
174        ${if not(tmeta(rpc(bypass_method_dispatch))) {
175            impl $crate::DynMethod for $ttype {
176                ${if tmeta(rpc(no_cancel)) {
177                    fn is_cancellable(&self) -> bool {
178                        false
179                    }
180                }}
181            }
182        } else if tmeta(rpc(no_method_name)) {
183            ${error "no_method_name is incompatible with bypass_method_dispatch."}
184        }}
185
186        ${select1 tmeta(rpc(method_name)) {
187            // Alas, `typetag does not work correctly when not in scope as `typetag`.
188            use $crate::typetag;
189            #[typetag::deserialize(name = ${tmeta(rpc(method_name)) as str})]
190            // Note that we do not support generics in method types.
191            // If we did, we would have to give each instantiation type its own method name.
192            impl $crate::DeserMethod for $ttype {
193                fn upcast_box(self: Box<Self>) -> Box<dyn $crate::DynMethod> {
194                    self as _
195                }
196            }
197            $crate::inventory::submit! {
198                $crate::MethodInfo_ {
199                    method_name : ${tmeta(rpc(method_name)) as str},
200                    typeid : std::any::TypeId::of::<$ttype>,
201                    output_name: std::any::type_name::<<$ttype as $crate::RpcMethod>::Output>,
202                    update_name: std::any::type_name::<<$ttype as $crate::RpcMethod>::Update>,
203                }
204            }
205        } else if tmeta(rpc(no_method_name)) {
206            // don't derive DeserMethod.
207        }}
208    };
209}
210pub use derive_deftly_template_DynMethod;
211
212use crate::{is_valid_rpc_identifier, InvalidRpcIdentifier, ObjectId};
213
214/// Return true if `name` is the name of some method.
215pub fn is_method_name(name: &str) -> bool {
216    /// Lazy set of all method names.
217    static METHOD_NAMES: Lazy<HashSet<&'static str>> = Lazy::new(|| iter_method_names().collect());
218    METHOD_NAMES.contains(name)
219}
220
221/// Return an iterator that yields every registered method name.
222///
223/// Used (e.g.) to enforce syntactic requirements on method names.
224pub fn iter_method_names() -> impl Iterator<Item = &'static str> {
225    inventory::iter::<MethodInfo_>().map(|mi| mi.method_name)
226}
227
228/// Given a type ID, return its RPC MethodInfo_ (if any).
229pub(crate) fn method_info_by_typeid(typeid: any::TypeId) -> Option<&'static MethodInfo_> {
230    /// Lazy map from TypeId to RPC method name.
231    static METHOD_INFO_BY_TYPEID: Lazy<HashMap<any::TypeId, &'static MethodInfo_>> =
232        Lazy::new(|| {
233            inventory::iter::<MethodInfo_>()
234                .map(|mi| ((mi.typeid)(), mi))
235                .collect()
236        });
237
238    METHOD_INFO_BY_TYPEID.get(&typeid).copied()
239}
240
241/// Check whether we have any method names that do not conform to our conventions.
242///
243/// Violations of these conventions won't stop the RPC system from working, but they may result in
244/// annoyances with namespacing, .
245///
246/// If provided, `additional_namespaces` is a list of namespaces other than our standard ones that
247/// we should accept.
248///
249/// Returns a `Vec` of method names that violate our rules, along with the rules that they violate.
250pub fn check_method_names<'a>(
251    additional_namespaces: impl IntoIterator<Item = &'a str>,
252) -> Vec<(&'static str, InvalidRpcIdentifier)> {
253    let mut recognized_namespaces: HashSet<&str> = additional_namespaces.into_iter().collect();
254    recognized_namespaces.extend(["arti", "rpc", "auth"]);
255
256    iter_method_names()
257        .filter_map(|name| {
258            is_valid_rpc_identifier(Some(&recognized_namespaces), name)
259                .err()
260                .map(|e| (name, e))
261        })
262        .collect()
263}