1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
#![doc = include_str!("../README.md")]
// @@ begin lint list maintained by maint/add_warning @@
#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
#![warn(missing_docs)]
#![warn(noop_method_call)]
#![warn(unreachable_pub)]
#![warn(clippy::all)]
#![deny(clippy::await_holding_lock)]
#![deny(clippy::cargo_common_metadata)]
#![deny(clippy::cast_lossless)]
#![deny(clippy::checked_conversions)]
#![warn(clippy::cognitive_complexity)]
#![deny(clippy::debug_assert_with_mut_call)]
#![deny(clippy::exhaustive_enums)]
#![deny(clippy::exhaustive_structs)]
#![deny(clippy::expl_impl_clone_on_copy)]
#![deny(clippy::fallible_impl_from)]
#![deny(clippy::implicit_clone)]
#![deny(clippy::large_stack_arrays)]
#![warn(clippy::manual_ok_or)]
#![deny(clippy::missing_docs_in_private_items)]
#![warn(clippy::needless_borrow)]
#![warn(clippy::needless_pass_by_value)]
#![warn(clippy::option_option)]
#![deny(clippy::print_stderr)]
#![deny(clippy::print_stdout)]
#![warn(clippy::rc_buffer)]
#![deny(clippy::ref_option_ref)]
#![warn(clippy::semicolon_if_nothing_returned)]
#![warn(clippy::trait_duplication_in_bounds)]
#![deny(clippy::unchecked_duration_subtraction)]
#![deny(clippy::unnecessary_wraps)]
#![warn(clippy::unseparated_literal_suffix)]
#![deny(clippy::unwrap_used)]
#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
#![allow(clippy::uninlined_format_args)]
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
pub mod dispatch;
mod err;
mod method;
mod obj;
use std::{convert::Infallible, sync::Arc};
pub use dispatch::{DispatchTable, InvokeError, UpdateSink};
pub use err::RpcError;
pub use method::{
check_method_names, is_method_name, iter_method_names, DeserMethod, DynMethod,
InvalidMethodName, Method, NoUpdates,
};
pub use obj::{Object, ObjectArcExt, ObjectId};
#[doc(hidden)]
pub use obj::cast::CastTable;
#[doc(hidden)]
pub use {
derive_deftly, dispatch::RpcResult, downcast_rs, erased_serde, futures, inventory,
method::MethodInfo_, once_cell, paste, tor_async_utils, tor_error::internal, typetag,
};
/// Templates for use with [`derive_deftly`]
pub mod templates {
pub use crate::method::derive_deftly_template_DynMethod;
pub use crate::obj::derive_deftly_template_Object;
}
/// An error returned from [`ContextExt::lookup`].
///
/// TODO RPC: This type should be made to conform with however we represent RPC
/// errors.
#[derive(Debug, Clone, thiserror::Error)]
#[non_exhaustive]
pub enum LookupError {
/// The specified object does not (currently) exist,
/// or the user does not have permission to access it.
#[error("No visible object with ID {0:?}")]
NoObject(ObjectId),
/// The specified object exists, but does not have the
/// expected type.
#[error("Unexpected type on object with ID {0:?}")]
WrongType(ObjectId),
}
/// A trait describing the context in which an RPC method is executed.
pub trait Context: Send + Sync {
/// Look up an object by identity within this context.
fn lookup_object(&self, id: &ObjectId) -> Result<Arc<dyn Object>, LookupError>;
/// Create an owning reference to `object` within this context.
///
/// Return an ObjectId for this object.
///
/// TODO RPC: We may need to change the above semantics and the name of this
/// function depending on how we decide to name and specify things.
fn register_owned(&self, object: Arc<dyn Object>) -> ObjectId;
/// Make sure that
/// this context contains a non-owning reference to `object`,
/// creating one if necessary.
///
/// Return an ObjectId for this object.
///
/// Note that this takes an Arc, since that's required in order to find a
/// working type Id for the target object.
///
/// TODO RPC: We may need to change the above semantics and the name of this
/// function depending on how we decide to name and specify things.
fn register_weak(&self, object: Arc<dyn Object>) -> ObjectId;
/// Drop an owning reference to the object called `object` within this context.
///
/// This will return an error if `object` is not an owning reference.
///
/// TODO RPC should this really return a LookupError?
fn release_owned(&self, object: &ObjectId) -> Result<(), LookupError>;
/// Return a dispatch table that can be used to invoke other RPC methods.
fn dispatch_table(&self) -> &Arc<std::sync::RwLock<DispatchTable>>;
}
/// An error caused while trying to send an update to a method.
///
/// These errors should be impossible in our current implementation, since they
/// can only happen if the `mpsc::Receiver` is closed—which can only happen
/// when the session loop drops it, which only happens when the session loop has
/// stopped polling its `FuturesUnordered` full of RPC request futures. Thus, any
/// `send` that would encounter this error should be in a future that is never
/// polled under circumstances when the error could happen.
///
/// Still, programming errors are real, so we are handling this rather than
/// declaring it a panic or something.
#[derive(Debug, Clone, thiserror::Error)]
#[non_exhaustive]
pub enum SendUpdateError {
/// The request was cancelled, or the connection was closed.
#[error("Unable to send on MPSC connection")]
ConnectionClosed,
}
impl From<Infallible> for SendUpdateError {
fn from(_: Infallible) -> Self {
unreachable!()
}
}
impl From<futures::channel::mpsc::SendError> for SendUpdateError {
fn from(_: futures::channel::mpsc::SendError) -> Self {
SendUpdateError::ConnectionClosed
}
}
/// Extension trait for [`Context`].
///
/// This is a separate trait so that `Context` can be object-safe.
pub trait ContextExt: Context {
/// Look up an object of a given type, and downcast it.
///
/// Return an error if the object can't be found, or has the wrong type.
fn lookup<T: Object>(&self, id: &ObjectId) -> Result<Arc<T>, LookupError> {
self.lookup_object(id)?
.downcast_arc()
.map_err(|_| LookupError::WrongType(id.clone()))
}
}
impl<T: Context> ContextExt for T {}
/// Try to find an appropriate function for calling a given RPC method on a
/// given RPC-visible object.
///
/// On success, return a Future.
///
/// Differs from using `DispatchTable::invoke()` in that it drops its lock
/// on the dispatch table before invoking the method.
pub fn invoke_rpc_method(
ctx: Arc<dyn Context>,
obj: Arc<dyn Object>,
method: Box<dyn DynMethod>,
sink: dispatch::BoxedUpdateSink,
) -> Result<dispatch::RpcResultFuture, InvokeError> {
// TODO RPC: Possibly, we should make this and `invoke_special_method` into
// methods on an extension trait of Arc<dyn Context>. We can't put them
// onto ContextExt, since they would impose a `Sized` requirement there.
// We also can't add inherent impls to Arc<dyn Context>.
let invocable = ctx
.dispatch_table()
.read()
.expect("poisoned lock")
.rpc_invoker(obj.as_ref(), method.as_ref())?;
invocable.invoke(obj, method, ctx, sink)
}
/// Invoke the given `method` on `obj` within `ctx`, and return its
/// actual result type.
///
/// Unlike `invoke_rpc_method`, this method does not return a type-erased result,
/// and does not require that the result can be serialized as an RPC object.
///
/// Differs from using `DispatchTable::invoke_special()` in that it drops its lock
/// on the dispatch table before invoking the method.
pub async fn invoke_special_method<M: Method>(
ctx: Arc<dyn Context>,
obj: Arc<dyn Object>,
method: Box<M>,
) -> Result<Box<Result<M::Output, M::Error>>, InvokeError> {
let invocable = ctx
.dispatch_table()
.read()
.expect("poisoned lock")
.special_invoker::<M>(obj.as_ref())?;
invocable
.invoke_special(obj, method, ctx)?
.await
.downcast()
.map_err(|_| InvokeError::Bug(tor_error::internal!("Downcast to wrong type")))
}
/// A serializable empty object.
///
/// Used when we need to declare that a method returns nothing.
///
/// TODO RPC: Perhaps we can get () to serialize as {} and make this an alias
/// for ().
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Default)]
#[non_exhaustive]
pub struct Nil {}
/// An instance of rpc::Nil.
pub const NIL: Nil = Nil {};
/// Common return type for RPC methods that return a single object ID
/// and nothing else.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, derive_more::From)]
pub struct SingletonId {
/// The ID of the object that we're returning.
id: ObjectId,
}