1
//! Method type for the RPC system.
2

            
3
use std::{
4
    any,
5
    collections::{HashMap, HashSet},
6
    sync::Arc,
7
};
8

            
9
use derive_deftly::define_derive_deftly;
10
use downcast_rs::Downcast;
11
use 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.
29
pub 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
42
    fn invoke_without_dispatch(
41
42
        &self,
42
42
        ctx: Arc<dyn crate::Context>,
43
42
        obj_id: &ObjectId,
44
42
    ) -> Result<crate::dispatch::RpcResultFuture, crate::InvokeError> {
45
42
        let _ = ctx;
46
42
        let _ = obj_id;
47
42
        Err(crate::InvokeError::NoDispatchBypass)
48
42
    }
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
}
61
downcast_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")]
70
pub 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>`
86
pub 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`.
99
pub 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

            
113
impl<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)]
122
pub 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)]
133
pub 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

            
146
inventory::collect!(MethodInfo_);
147

            
148
define_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
4
            #[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
}
210
pub use derive_deftly_template_DynMethod;
211

            
212
use crate::{is_valid_rpc_identifier, InvalidRpcIdentifier, ObjectId};
213

            
214
/// Return true if `name` is the name of some method.
215
175
pub fn is_method_name(name: &str) -> bool {
216
    /// Lazy set of all method names.
217
35
    static METHOD_NAMES: Lazy<HashSet<&'static str>> = Lazy::new(|| iter_method_names().collect());
218
175
    METHOD_NAMES.contains(name)
219
175
}
220

            
221
/// Return an iterator that yields every registered method name.
222
///
223
/// Used (e.g.) to enforce syntactic requirements on method names.
224
70
pub fn iter_method_names() -> impl Iterator<Item = &'static str> {
225
877
    inventory::iter::<MethodInfo_>().map(|mi| mi.method_name)
226
70
}
227

            
228
/// Given a type ID, return its RPC MethodInfo_ (if any).
229
2
pub(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
2
        Lazy::new(|| {
233
2
            inventory::iter::<MethodInfo_>()
234
4
                .map(|mi| ((mi.typeid)(), mi))
235
2
                .collect()
236
2
        });
237

            
238
2
    METHOD_INFO_BY_TYPEID.get(&typeid).copied()
239
2
}
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.
250
2
pub fn check_method_names<'a>(
251
2
    additional_namespaces: impl IntoIterator<Item = &'a str>,
252
2
) -> Vec<(&'static str, InvalidRpcIdentifier)> {
253
2
    let mut recognized_namespaces: HashSet<&str> = additional_namespaces.into_iter().collect();
254
2
    recognized_namespaces.extend(["arti", "rpc", "auth"]);
255
2

            
256
2
    iter_method_names()
257
26
        .filter_map(|name| {
258
26
            is_valid_rpc_identifier(Some(&recognized_namespaces), name)
259
26
                .err()
260
26
                .map(|e| (name, e))
261
26
        })
262
2
        .collect()
263
2
}