Expand description
§tor-rpcbase
Backend for Arti’s RPC service
§Overview
Arti’s RPC subsystem centers around the idea of calling methods to objects, and receiving asynchronous replies to those method calls.
In this crate, we define the APIs to implement those methods and objects. This is a low-level crate, since we want to be able to define objects and methods throughout the arti codebase in the places that are most logical.
§Key concepts
An RPC session is implemented as a bytestream encoding a series of I-JSON (RFC7493) messages. Each message from the application describes a method to invoke on an object. In response to such a message, Arti replies asynchronously with zero or more “update messages”, and up to one final “reply” or “error” message.
This crate defines the mechanisms for defining these objects and methods in Rust.
An Object is a value
that can participate in the RPC API
as the target of messages.
To be an Object,
a value must implement the Object
trait.
Objects should be explicitly stored in an Arc
whenever possible.
In order to use object,
an RPC client must have an ObjectId
referring to that object.
We say that such an object is “visible” on the client’s session.
Not all objects are visible to all clients.
Each method is defined as a Rust type
that’s an instant of DynMethod
.
The method’s arguments are the type’s fields.
Its return value is an associated type in the DynMethod
trait.
Each method will typically have an associated output type,
error type,
and optional update type,
all defined by having the method implement the Method
trait.
In order to be invoked from an RPC session,
the method must additionally implement DeserMethod
which additionally requires that the method
and its associated types.
(Method that do not have this property
are called “special methods”;
they can only be invoked from outside Rust.)
Once a method and an object both exist,
it’s possible to define an implementation of the method
on the object.
This is done by writing an async fn
taking the
object and method types as arguments,
and later registering that async fn
using
static_rpc_invoke_fn!
or DispatchTable::extend
.
These implementation functions additionally take as arguments
a Context
, which defines an interface to the RPC session,
and an optional UpdateSink
,
which is used to send incremental update messages.
§Example
use derive_deftly::Deftly;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tor_rpcbase as rpc;
// Here we declare that Cat is an Object.
// This lets us make Cats visible to the RPC system.
#[derive(Deftly)]
#[derive_deftly(rpc::Object)]
pub struct Cat {}
// Here we define a Speak method, reachable via the
// RPC method name "x-example:speak", taking a single argument.
#[derive(Deftly, Deserialize, Debug)]
#[derive_deftly(rpc::DynMethod)]
#[deftly(rpc(method_name = "x-example:speak"))]
pub struct Speak {
message: String,
}
// We define a type type to represent the output of the method.
#[derive(Debug, Serialize)]
pub struct SpeechReply {
speech: String,
}
// We declare that "Speak" will always have a given set of
// possible output, update, and error types.
impl rpc::RpcMethod for Speak {
type Output = SpeechReply;
type Update = rpc::NoUpdates;
}
// We write a function with this signature to implement `Speak` for `Cat`.
async fn speak_for_cat(
cat: Arc<Cat>,
method: Box<Speak>,
_context: Arc<dyn rpc::Context>
) -> Result<SpeechReply, rpc::RpcError> {
Ok(SpeechReply {
speech: format!(
"meow meow {} meow", method.message
)
})
}
// We register `speak_for_cat` as an RPC implementation function.
rpc::static_rpc_invoke_fn!{
speak_for_cat;
}
§How it works
The key type in this crate is DispatchTable
;
it stores a map from (method, object)
type pairs
to type-erased invocation functions
(implementations of dispatch::RpcInvocable
).
When it’s time to invoke a method on an object,
the RPC session uses invoke_rpc_method
with a type-erased Object
and DynMethod
.
The DispatchTable
is then used to look up
the appropriate RpcInvocable
and
call it on the provided arguments.
How are the type-erased RpcInvocable
functions created?
They are created automatically from appropriate async fn()
s
due to blanket implementations of RpcInvocable
for Fn()
s with appropriate types.
§Caveat: The orphan rule is not enforced on RPC methods
This crate allows any other crate to define an RPC method on an RPC-visible object, even if the method type and object type are declared in another crate.
You need to be careful with this capability: such externally added methods will cause the RPC subsystem to break (and refuse to start up!) in the future, if Arti later defines the same method on the same object.
When adding new RPC methods outside Arti,
it is best to either define existing RPC methods on your objects,
or to define your own RPC methods (outside of the arti:
namespace)
on Arti’s objects.
§Caveat: Be careful around the capability system
Arti’s RPC model assumes that objects are capabilities:
if you have a working ObjectId
for an Object
,
you are allowed to invoke all its methods.
The RPC system keeps its clients isolated from one another
by not giving them Ids for one another’s objects,
and by not giving them access to global state
that would allow them to affect one another inappropriately.
This practice is easy to violate when you add new methods: Arti’s Rust API permits some operations that should only be allowed to RPC superusers.
Therefore, when defining methods, make sure that you are requiring some object that “belongs” to a given RPC session, and that you are not affecting objects that belong to other RPC sessions.
§Related crates
See also:
arti-rpcserver
, which actually implements the RPC protocol, sessions, and objectId mappings.arti
, where RPC sessions are created based on incoming connections to an RPC socket.- Uses of
Object
orDynMethod
throughout other arti crates.
License: MIT OR Apache-2.0
Re-exports§
pub use dispatch::DispatchTable;
pub use dispatch::InvokeError;
pub use dispatch::UpdateSink;
Modules§
- dispatch
- A multiple-argument dispatch system for our RPC system.
- templates
- Templates for use with [
derive_deftly
]
Macros§
- derive_
deftly_ template_ DynMethod - Declare that one or more space-separated types should be considered as dynamically dispatchable RPC methods.
- derive_
deftly_ template_ Object - Allow a type to participate as an Object in the RPC system.
- invoker_
ent - Create an
InvokerEnt
around a single function. - invoker_
ent_ list - Crate a
Vec<
ofInvokerEnt
. - static_
rpc_ invoke_ fn - Cause one or more RPC functions to be statically registered, each for handling a single Method on a single Object type.
Structs§
- Nil
- A serializable empty object.
- Object
Id - An identifier for an Object within the context of a Session.
- RpcDispatch
Information describe-methods
- A table describing the the set of RPC methods available, which types they expect and return, and which objects they apply to.
- RpcError
- An error type returned by failing RPC methods.
- Single
IdResponse - Common return type for RPC methods that return a single object ID and nothing else.
Enums§
- Invalid
RpcIdentifier - Error representing an “invalid” RPC identifier.
- Lookup
Error - An error returned from
ContextExt::lookup
. - NoUpdates
- An uninhabited type, used to indicate that a given method will never send updates.
- RpcError
Kind - Error kinds for RPC errors.
- Send
Update Error - An error caused while trying to send an update to a method.
Constants§
- NIL
- An instance of rpc::Nil.
Traits§
- Context
- A trait describing the context in which an RPC method is executed.
- Context
Ext - Extension trait for
Context
. - Deser
Method - A DynMethod that can be deserialized.
- DynMethod
- The parameters and method name associated with a given Request.
- Method
- A typed method, used to ensure that all implementations of a method have the same success and updates types.
- Object
- An object in our RPC system to which methods can be addressed.
- Object
ArcExt - Extension trait for
Arc<dyn Object>
to support convenient downcasting todyn Trait
. - RpcMethod
- A method that can be invoked from the RPC system.
Functions§
- check_
method_ names - Check whether we have any method names that do not conform to our conventions.
- invoke_
rpc_ method - Try to find an appropriate function for calling a given RPC method on a given RPC-visible object.
- invoke_
special_ method - Invoke the given
method
onobj
withinctx
, and return its actual result type. - is_
method_ name - Return true if
name
is the name of some method. - iter_
method_ names - Return an iterator that yields every registered method name.