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}