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