obj.rs
1 //! Object type for our RPC system. 2 3 pub(crate) mod cast; 4 5 use std::sync::Arc; 6 7 use derive_deftly::define_derive_deftly; 8 use downcast_rs::DowncastSync; 9 use serde::{Deserialize, Serialize}; 10 11 use self::cast::CastTable; 12 13 /// An object in our RPC system to which methods can be addressed. 14 /// 15 /// You shouldn't implement this trait yourself; instead, use the 16 /// [`derive_deftly(Object)`]. 17 /// 18 /// See the documentation for [`derive_deftly(Object)`] 19 /// for examples of how to declare and 20 /// downcast `Object`s. 21 /// 22 /// [`derive_deftly(Object)`]: crate::templates::derive_deftly_template_Object 23 pub trait Object: DowncastSync + Send + Sync + 'static { 24 /// Return true if this object should be given an identifier that allows it 25 /// to be used outside of the session that generated it. 26 /// 27 /// Currently, the only use for such IDs in arti is identifying stream 28 /// contexts in when opening a SOCKS connection: When an application opens a 29 /// stream, it needs to declare what RPC context (like a `TorClient`) it's 30 /// using, which requires that some identifier for that context exist 31 /// outside of the RPC session that owns it. 32 fn expose_outside_of_session(&self) -> bool { 33 false 34 } 35 36 /// Return a [`CastTable`] that can be used to downcast a `dyn Object` of 37 /// this type into various kinds of `dyn Trait` references. 38 /// 39 /// The default implementation of this method declares that the `Object` 40 /// can't be downcast into any traits. 41 /// 42 /// You should not implement this method yourself; instead use 43 /// [`derive_deftly(Object)`](crate::templates::derive_deftly_template_Object). 44 fn get_cast_table(&self) -> &CastTable { 45 &cast::EMPTY_CAST_TABLE 46 } 47 48 /// Optionally, return a delegation target for this `Object``. 49 /// 50 /// If method lookup fails on this object, then the `delegate` 51 fn delegate(&self) -> Option<Arc<dyn Object>> { 52 None 53 } 54 } 55 downcast_rs::impl_downcast!(sync Object); 56 57 /// An identifier for an Object within the context of a Session. 58 /// 59 /// These are opaque from the client's perspective. 60 #[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize)] 61 #[serde(transparent)] 62 pub struct ObjectId( 63 // (We use Box<str> to save a word here, since these don't have to be 64 // mutable ever.) 65 Box<str>, 66 ); 67 68 impl AsRef<str> for ObjectId { 69 fn as_ref(&self) -> &str { 70 self.0.as_ref() 71 } 72 } 73 74 impl<T> From<T> for ObjectId 75 where 76 T: Into<Box<str>>, 77 { 78 fn from(value: T) -> Self { 79 Self(value.into()) 80 } 81 } 82 83 /// Extension trait for `Arc<dyn Object>` to support convenient 84 /// downcasting to `dyn Trait`. 85 /// 86 /// You don't need to use this for downcasting to an object's concrete 87 /// type; for that, use [`downcast_rs::DowncastSync`]. 88 /// 89 /// # Examples 90 /// 91 /// ``` 92 /// use tor_rpcbase::{Object, ObjectArcExt, templates::*}; 93 /// use derive_deftly::Deftly; 94 /// use std::sync::Arc; 95 /// 96 /// #[derive(Deftly)] 97 /// #[derive_deftly(Object)] 98 /// #[deftly(rpc(downcastable_to = "HasFeet"))] 99 /// pub struct Frog {} 100 /// pub trait HasFeet { 101 /// fn num_feet(&self) -> usize; 102 /// } 103 /// impl HasFeet for Frog { 104 /// fn num_feet(&self) -> usize { 4 } 105 /// } 106 /// 107 /// /// If `obj` is a HasFeet, return how many feet it has. 108 /// /// Otherwise, return 0. 109 /// fn check_feet(obj: Arc<dyn Object>) -> usize { 110 /// let maybe_has_feet: Option<&dyn HasFeet> = obj.cast_to_trait(); 111 /// match maybe_has_feet { 112 /// Some(foot_haver) => foot_haver.num_feet(), 113 /// None => 0, 114 /// } 115 /// } 116 /// 117 /// assert_eq!(check_feet(Arc::new(Frog{})), 4); 118 /// ``` 119 pub trait ObjectArcExt { 120 /// Try to cast this `Arc<dyn Object>` to a `T`. On success, return a reference to 121 /// T; on failure, return None. 122 fn cast_to_trait<T: ?Sized + 'static>(&self) -> Option<&T>; 123 124 /// Try to cast this `Arc<dyn Object>` to an `Arc<T>`. 125 fn cast_to_arc_trait<T: ?Sized + 'static>(self) -> Result<Arc<T>, Arc<dyn Object>>; 126 } 127 128 impl dyn Object { 129 /// Try to cast this `Object` to a `T`. On success, return a reference to 130 /// T; on failure, return None. 131 /// 132 /// This method is only for casting to `&dyn Trait`; 133 /// see [`ObjectArcExt`] for limitations. 134 pub fn cast_to_trait<T: ?Sized + 'static>(&self) -> Option<&T> { 135 let table = self.get_cast_table(); 136 table.cast_object_to(self) 137 } 138 } 139 140 impl ObjectArcExt for Arc<dyn Object> { 141 fn cast_to_trait<T: ?Sized + 'static>(&self) -> Option<&T> { 142 let obj: &dyn Object = self.as_ref(); 143 obj.cast_to_trait() 144 } 145 fn cast_to_arc_trait<T: ?Sized + 'static>(self) -> Result<Arc<T>, Arc<dyn Object>> { 146 let table = self.get_cast_table(); 147 table.cast_object_to_arc(self.clone()) 148 } 149 } 150 151 define_derive_deftly! { 152 /// Allow a type to participate as an Object in the RPC system. 153 /// 154 /// This template implements `Object` for the 155 /// target type, and can be used to cause objects to participate in the trait 156 /// downcasting system. 157 /// 158 /// # Examples 159 /// 160 /// ## Simple case, just implements `Object`. 161 /// 162 /// ``` 163 /// use tor_rpcbase::{self as rpc, templates::*}; 164 /// use derive_deftly::Deftly; 165 /// 166 /// #[derive(Default, Deftly)] 167 /// #[derive_deftly(Object)] 168 /// struct Houseplant { 169 /// oxygen_per_sec: f64, 170 /// benign_neglect: u8 171 /// } 172 /// 173 /// // You can downcast an Object to a concrete type. 174 /// use downcast_rs::DowncastSync; 175 /// use std::sync::Arc; 176 /// let plant_obj: Arc<dyn rpc::Object> = Arc::new(Houseplant::default()); 177 /// let as_plant: Arc<Houseplant> = plant_obj.downcast_arc().ok().unwrap(); 178 /// ``` 179 /// 180 /// ## With trait downcasting 181 /// 182 /// By default, you can use [`downcast_rs`] to downcast a `dyn Object` to its 183 /// concrete type. If you also need to be able to downcast a `dyn Object` to a given 184 /// trait that it implements, you can use the `downcastable_to` attributes for `Object` to have 185 /// it participate in trait downcasting: 186 /// 187 /// ``` 188 /// use tor_rpcbase::{self as rpc, templates::*}; 189 /// use derive_deftly::Deftly; 190 /// 191 /// #[derive(Deftly)] 192 /// #[derive_deftly(Object)] 193 /// #[deftly(rpc(downcastable_to = "Gizmo, Doodad"))] 194 /// struct Frobnitz {} 195 /// 196 /// trait Gizmo {} 197 /// trait Doodad {} 198 /// impl Gizmo for Frobnitz {} 199 /// impl Doodad for Frobnitz {} 200 /// 201 /// use std::sync::Arc; 202 /// use rpc::ObjectArcExt; // for the cast_to method. 203 /// let frob_obj: Arc<dyn rpc::Object> = Arc::new(Frobnitz {}); 204 /// let gizmo: &dyn Gizmo = frob_obj.cast_to_trait().unwrap(); 205 /// let doodad: &dyn Doodad = frob_obj.cast_to_trait().unwrap(); 206 /// ``` 207 /// 208 /// ## With generic objects 209 /// 210 /// Right now, a generic object can't participate in our method lookup system, 211 /// but it _can_ participate in trait downcasting. We'll try to remove this 212 /// limitation in the future. 213 /// 214 /// ``` 215 /// use tor_rpcbase::{self as rpc, templates::*}; 216 /// use derive_deftly::Deftly; 217 /// 218 /// #[derive(Deftly)] 219 /// #[derive_deftly(Object)] 220 /// #[deftly(rpc(downcastable_to = "ExampleTrait"))] 221 /// struct Generic<T,U> where T:Clone, U:PartialEq { 222 /// t: T, 223 /// u: U, 224 /// } 225 /// 226 /// trait ExampleTrait {} 227 /// impl<T:Clone,U:PartialEq> ExampleTrait for Generic<T,U> {} 228 /// 229 /// use std::sync::Arc; 230 /// use rpc::ObjectArcExt; // for the cast_to method. 231 /// let obj: Arc<dyn rpc::Object> = Arc::new(Generic { t: 42_u8, u: 42_u8 }); 232 /// let tr: &dyn ExampleTrait = obj.cast_to_trait().unwrap(); 233 /// ``` 234 /// 235 /// ## Making an object "exposed outside of the session" 236 /// 237 /// You can flag any kind of Object so that its identifiers will be exported 238 /// outside of the local RPC session. (Arti uses this for Objects whose 239 /// ObjectId needs to be used as a SOCKS identifier.) To do so, 240 /// use the `expose_outside_session` attribute: 241 /// 242 /// ``` 243 /// use tor_rpcbase::{self as rpc, templates::*}; 244 /// use derive_deftly::Deftly; 245 /// 246 /// #[derive(Deftly)] 247 /// #[derive_deftly(Object)] 248 /// #[deftly(rpc(expose_outside_of_session))] 249 /// struct Visible {} 250 /// ``` 251 /// 252 /// ## Delegation 253 /// 254 /// You can give an Object the ability to delegate 255 /// method invocations to another object it contains. 256 /// The inner object must be an `Arc`. 257 /// To do so, use the `delegate_with` attribute. 258 /// The attribute must contain an expression of type 259 /// `FnOnce(&Self) -> Option(Arc<T>)`, where T implements Object. 260 /// 261 /// ``` 262 /// use tor_rpcbase::{self as rpc, templates::*}; 263 /// use derive_deftly::Deftly; 264 /// use std::sync::Arc; 265 /// 266 /// #[derive(Deftly)] 267 /// #[derive_deftly(Object)] 268 /// struct Inner {} 269 /// 270 /// #[derive(Deftly)] 271 /// #[derive_deftly(Object)] 272 /// #[deftly(rpc( 273 /// delegate_with="|this: &Self| Some(this.inner.clone())", 274 /// delegate_type="Inner" 275 /// ))] 276 /// struct Outer { 277 /// inner: Arc<Inner>, 278 /// } 279 /// ``` 280 /// 281 export Object expect items: 282 283 284 impl<$tgens> $ttype where 285 // We need this restriction in case there are generics 286 // that might not impl these traits. 287 $ttype: Send + Sync + 'static, 288 $twheres 289 { 290 /// Construct a new `CastTable` for this type. 291 /// 292 /// This is a function so that we can call it multiple times as 293 /// needed if the type is generic. 294 /// 295 /// Don't invoke this yourself; instead use `decl_object!`. 296 #[doc(hidden)] 297 fn make_cast_table() -> $crate::CastTable { 298 ${if tmeta(rpc(downcastable_to)) { 299 $crate::cast_table_deftness_helper!{ 300 // TODO ideally we would support multiple downcastable_to rather 301 // than a single list, and use `as ty` 302 ${tmeta(rpc(downcastable_to)) as token_stream} 303 } 304 } else { 305 $crate::CastTable::default() 306 }} 307 } 308 } 309 310 ${if tmeta(rpc(delegate_type)) { 311 $crate::register_delegation_note!( 312 $ttype, 313 ${tmeta(rpc(delegate_type )) as ty} 314 ); 315 }} 316 317 ${if tmeta(rpc(delegate_type)) { 318 #[doc = "Delegates to [`"] 319 #[doc = ${tmeta(rpc(delegate_type)) as str}] 320 #[doc = "`]"] 321 }} 322 impl<$tgens> $crate::Object for $ttype where 323 // We need this restriction in case there are generics 324 // that might not impl these traits. 325 $ttype: Send + Sync + 'static, 326 $twheres 327 { 328 ${if tmeta(rpc(expose_outside_of_session)) { 329 fn expose_outside_of_session(&self) -> bool { 330 true 331 } 332 }} 333 334 ${if tmeta(rpc(delegate_with)) { 335 fn delegate(&self) -> Option<Arc<dyn $crate::Object>> { 336 let r: Option<Arc<${tmeta(rpc(delegate_type)) as ty}>> = (${tmeta(rpc(delegate_with)) as expr})(self); 337 338 r.map(|v| v as Arc<dyn $crate::Object>) 339 } 340 }} 341 342 fn get_cast_table(&self) -> &$crate::CastTable { 343 ${if tgens { 344 // For generic types, we have a potentially unbounded number 345 // of CastTables: one for each instantiation of the type. 346 // Therefore we keep a mutable add-only HashMap of CastTables. 347 348 use $crate::once_cell::sync::Lazy; 349 use std::sync::RwLock; 350 use std::collections::HashMap; 351 use std::any::TypeId; 352 // Map from concrete type to CastTable. 353 // 354 // Note that we use `&'static CastTable` here, not 355 // `Box<CastTable>`: If we used Box<>, the borrow checker would 356 // worry that our `CastTable`s might get freed after we returned 357 // a reference to them. Using `&'static` guarantees that the CastTable 358 // references are safe to return. 359 // 360 // In order to get a `&'static`, we need to use Box::leak(). 361 // That's fine, since we only create one CastTable per 362 // instantiation of the type. 363 static TABLES: Lazy<RwLock<HashMap<TypeId, &'static $crate::CastTable>>> = 364 Lazy::new(|| RwLock::new(HashMap::new())); 365 { 366 let tables_r = TABLES.read().expect("poisoned lock"); 367 if let Some(table) = tables_r.get(&TypeId::of::<Self>()) { 368 // Fast case: we already had a CastTable for this instantiation. 369 table 370 } else { 371 // We didn't find a CastTable. 372 drop(tables_r); // prevent deadlock. 373 TABLES 374 .write() 375 .expect("poisoned lock") 376 .entry(TypeId::of::<Self>()) 377 // We use `or_insert_with` here to avoid a race 378 // condition: we only want to call make_cast_table if 379 // one didn't already exist. 380 .or_insert_with(|| Box::leak(Box::new(Self::make_cast_table()))) 381 } 382 } 383 } else { 384 // For non-generic types, we only ever have a single CastTable, 385 // so we can just construct it once and return it. 386 use $crate::once_cell::sync::Lazy; 387 static TABLE: Lazy<$crate::CastTable> = Lazy::new(|| $ttype::make_cast_table()); 388 &TABLE 389 }} 390 } 391 } 392 } 393 pub use derive_deftly_template_Object; 394 395 #[cfg(test)] 396 mod test { 397 // @@ begin test lint list maintained by maint/add_warning @@ 398 #![allow(clippy::bool_assert_comparison)] 399 #![allow(clippy::clone_on_copy)] 400 #![allow(clippy::dbg_macro)] 401 #![allow(clippy::mixed_attributes_style)] 402 #![allow(clippy::print_stderr)] 403 #![allow(clippy::print_stdout)] 404 #![allow(clippy::single_char_pattern)] 405 #![allow(clippy::unwrap_used)] 406 #![allow(clippy::unchecked_duration_subtraction)] 407 #![allow(clippy::useless_vec)] 408 #![allow(clippy::needless_pass_by_value)] 409 //! <!-- @@ end test lint list maintained by maint/add_warning @@ --> 410 411 use super::*; 412 use derive_deftly::Deftly; 413 414 #[derive(Deftly)] 415 #[derive_deftly(Object)] 416 #[deftly(rpc(downcastable_to = "HasWheels"))] 417 struct Bicycle {} 418 trait HasWheels { 419 fn num_wheels(&self) -> usize; 420 } 421 impl HasWheels for Bicycle { 422 fn num_wheels(&self) -> usize { 423 2 424 } 425 } 426 427 #[derive(Deftly)] 428 #[derive_deftly(Object)] 429 struct Opossum {} 430 431 #[test] 432 fn standard_cast() { 433 let bike = Bicycle {}; 434 let erased_bike: &dyn Object = &bike; 435 let has_wheels: &dyn HasWheels = erased_bike.cast_to_trait().unwrap(); 436 assert_eq!(has_wheels.num_wheels(), 2); 437 438 let pogo = Opossum {}; 439 let erased_pogo: &dyn Object = &pogo; 440 let has_wheels: Option<&dyn HasWheels> = erased_pogo.cast_to_trait(); 441 assert!(has_wheels.is_none()); 442 } 443 444 #[derive(Deftly)] 445 #[derive_deftly(Object)] 446 #[deftly(rpc(downcastable_to = "HasWheels"))] 447 struct Crowd<T: HasWheels + Send + Sync + 'static> { 448 members: Vec<T>, 449 } 450 impl<T: HasWheels + Send + Sync> HasWheels for Crowd<T> { 451 fn num_wheels(&self) -> usize { 452 self.members.iter().map(T::num_wheels).sum() 453 } 454 } 455 456 #[test] 457 fn generic_cast() { 458 let bikes = Crowd { 459 members: vec![Bicycle {}, Bicycle {}], 460 }; 461 let erased_bikes: &dyn Object = &bikes; 462 let has_wheels: &dyn HasWheels = erased_bikes.cast_to_trait().unwrap(); 463 assert_eq!(has_wheels.num_wheels(), 4); 464 465 let arc_bikes = Arc::new(bikes); 466 let erased_arc_bytes: Arc<dyn Object> = arc_bikes.clone(); 467 let arc_has_wheels: Arc<dyn HasWheels> = 468 erased_arc_bytes.clone().cast_to_arc_trait().ok().unwrap(); 469 assert_eq!(arc_has_wheels.num_wheels(), 4); 470 471 let ref_has_wheels: &dyn HasWheels = erased_arc_bytes.cast_to_trait().unwrap(); 472 assert_eq!(ref_has_wheels.num_wheels(), 4); 473 474 trait SomethingElse {} 475 let arc_something_else: Result<Arc<dyn SomethingElse>, _> = 476 erased_arc_bytes.clone().cast_to_arc_trait(); 477 let err_arc = arc_something_else.err().unwrap(); 478 assert!(Arc::ptr_eq(&err_arc, &erased_arc_bytes)); 479 } 480 481 #[derive(Deftly)] 482 #[derive_deftly(Object)] 483 #[deftly(rpc(delegate_with = "|cage: &Self| Some(cage.possum.clone())"))] 484 #[deftly(rpc(delegate_type = "Opossum"))] 485 struct PossumCage { 486 possum: Arc<Opossum>, 487 } 488 }