/ crates / tor-rpcbase / README.md
README.md
  1  # tor-rpcbase
  2  
  3  Backend for Arti's RPC service
  4  
  5  ## Overview
  6  
  7  Arti's RPC subsystem centers around the idea
  8  of calling methods to objects,
  9  and receiving asynchronous replies to those method calls.
 10  
 11  In this crate, we define the APIs
 12  to implement those methods and objects.
 13  This is a low-level crate, since we want to be able to define objects
 14  and methods throughout the arti codebase
 15  in the places that are most logical.
 16  
 17  ## Key concepts
 18  
 19  An RPC session is implemented as a bytestream
 20  encoding a series of [I-JSON] (RFC7493) messages.
 21  Each message from the application
 22  describes a _method_ to invoke on an _object_.
 23  In response to such a message,
 24  Arti replies asynchronously with zero or more "update messages",
 25  and up to one final "reply" or "error" message.
 26  
 27  This crate defines the mechanisms
 28  for defining these objects and methods in Rust.
 29  
 30  An Object is a value
 31  that can participate in the RPC API
 32  as the target of messages.
 33  To be an Object,
 34  a value must implement the [`Object`] trait.
 35  Objects should be explicitly stored in an `Arc`
 36  whenever possible.
 37  
 38  In order to use object,
 39  an RPC client must have an [`ObjectId`] referring to that object.
 40  We say that such an object is "visible" on the client's session.
 41  Not all objects are visible to all clients.
 42  
 43  Each method is defined as a Rust type
 44  that's an instant of [`DynMethod`].
 45  The method's arguments are the type's fields.
 46  Its return value is an associated type in the `DynMethod` trait.
 47  Each method will typically have an associated output type,
 48  error type,
 49  and optional update type,
 50  all defined by having the method implement the [`Method`] trait.
 51  
 52  In order to be invoked from an RPC session,
 53  the method must additionally implement [`DeserMethod`]
 54  which additionally requires that the method
 55  and its associated types.
 56  (Method that do not have this property
 57  are called "special methods";
 58  they can only be invoked from outside Rust.)
 59  
 60  Once a method and an object both exist,
 61  it's possible to define an implementation of the method
 62  on the object.
 63  This is done by writing an `async fn` taking the
 64  object and method types as arguments,
 65  and later registering that `async fn` using
 66  [`static_rpc_invoke_fn!`] or [`DispatchTable::extend`].
 67  
 68  These implementation functions additionally take as arguments
 69  a [`Context`], which defines an interface to the RPC session,
 70  and an optional [`UpdateSink`],
 71  which is used to send incremental update messages.
 72  
 73  ## Example
 74  
 75  ```rust
 76  use derive_deftly::Deftly;
 77  use serde::{Deserialize, Serialize};
 78  use std::sync::Arc;
 79  use tor_rpcbase as rpc;
 80  
 81  // Here we declare that Cat is an Object.
 82  // This lets us make Cats visible to the RPC system.
 83  #[derive(Deftly)]
 84  #[derive_deftly(rpc::Object)]
 85  pub struct Cat {}
 86  
 87  // Here we define a Speak method, reachable via the
 88  // RPC method name "x-example:speak", taking a single argument.
 89  #[derive(Deftly, Deserialize, Debug)]
 90  #[derive_deftly(rpc::DynMethod)]
 91  #[deftly(rpc(method_name = "x-example:speak"))]
 92  pub struct Speak {
 93      message: String,
 94  }
 95  
 96  // We define a type type to represent the output of the method.
 97  #[derive(Debug, Serialize)]
 98  pub struct SpeechReply {
 99      speech: String,
100  }
101  
102  // We declare that "Speak" will always have a given set of
103  // possible output, update, and error types.
104  impl rpc::RpcMethod for Speak {
105      type Output = SpeechReply;
106      type Update = rpc::NoUpdates;
107  }
108  
109  // We write a function with this signature to implement `Speak` for `Cat`.
110  async fn speak_for_cat(
111      cat: Arc<Cat>,
112      method: Box<Speak>,
113      _context: Arc<dyn rpc::Context>
114  ) -> Result<SpeechReply, rpc::RpcError> {
115      Ok(SpeechReply {
116          speech: format!(
117              "meow meow {} meow", method.message
118          )
119      })
120  }
121  
122  // We register `speak_for_cat` as an RPC implementation function.
123  rpc::static_rpc_invoke_fn!{
124      speak_for_cat;
125  }
126  ```
127  
128  
129  
130  ## How it works
131  
132  The key type in this crate is [`DispatchTable`];
133  it stores a map from `(method, object)` type pairs
134  to type-erased invocation functions
135  (implementations of [`dispatch::RpcInvocable`]).
136  When it's time to invoke a method on an object,
137  the RPC session uses [`invoke_rpc_method`]
138  with a type-erased [`Object`] and [`DynMethod`].
139  The `DispatchTable` is then used to look up
140  the appropriate `RpcInvocable` and
141  call it on the provided arguments.
142  
143  How are the type-erased `RpcInvocable` functions created?
144  They are created automatically from appropriate `async fn()`s
145  due to blanket implementations of `RpcInvocable`
146  for `Fn()`s with appropriate types.
147  
148  ## Caveat: The orphan rule is not enforced on RPC methods
149  
150  This crate allows any other crate to define an RPC method on an RPC-visible object,
151  even if the method type and object type are declared in another crate.
152  
153  You need to be careful with this capability:
154  such externally added methods will cause the RPC subsystem to break
155  (and refuse to start up!)
156  in the future,
157  if Arti later defines the same method on the same object.
158  
159  When adding new RPC methods outside Arti,
160  it is best to either define existing RPC methods on your objects,
161  or to define your own RPC methods (outside of the `arti:` namespace)
162  on Arti's objects.
163  
164  ## Caveat: Be careful around the capability system
165  
166  Arti's RPC model assumes that _objects are capabilities_:
167  if you have a working [`ObjectId`] for an `Object`,
168  you are allowed to invoke all its methods.
169  The RPC system keeps its clients isolated from one another
170  by not giving them Ids for one another's objects,
171  and by not giving them access to global state
172  that would allow them to affect one another inappropriately.
173  
174  This practice is easy to violate when you add new methods:
175  Arti's Rust API permits some operations
176  that should only be allowed to RPC superusers.
177  
178  Therefore, when defining methods, make sure that you are
179  requiring some object that "belongs" to a given RPC session,
180  and that you are not affecting objects that belong to other RPC sessions.
181  
182  ## Related crates
183  
184  See also:
185    * `arti-rpcserver`, which actually implements the RPC protocol,
186      sessions, and objectId mappings.
187    * `arti`, where RPC sessions are created based on incoming connections to
188      an RPC socket.
189    * Uses of `Object` or `DynMethod` throughout other arti crates.
190  
191  
192  [I-JSON]: https://datatracker.ietf.org/doc/html/rfc7493
193  
194  License: MIT OR Apache-2.0