1
//! Top-level `RpcMgr` to launch sessions.
2

            
3
use std::sync::{Arc, Mutex, RwLock, Weak};
4

            
5
use rand::Rng;
6
use rpc::InvalidRpcIdentifier;
7
use tor_rpcbase as rpc;
8
use tracing::warn;
9

            
10
use crate::{
11
    RpcAuthentication,
12
    connection::{Connection, ConnectionId},
13
    globalid::{GlobalId, MacKey},
14
};
15

            
16
/// Alias to force use of RandomState, regardless of features enabled in `weak_tables`.
17
///
18
/// See <https://github.com/tov/weak-table-rs/issues/23> for discussion.
19
type WeakValueHashMap<K, V> = weak_table::WeakValueHashMap<K, V, std::hash::RandomState>;
20

            
21
/// Shared state, configuration, and data for all RPC sessions.
22
///
23
/// An RpcMgr knows how to listen for incoming RPC connections, and launch sessions based on them.
24
pub struct RpcMgr {
25
    /// A key that we use to ensure that identifiers are unforgeable.
26
    ///
27
    /// When giving out a global (non-session-bound) identifier, we use this key
28
    /// to authenticate the identifier when it's given back to us.
29
    ///
30
    /// We make copies of this key when constructing a session.
31
    global_id_mac_key: MacKey,
32

            
33
    /// Our reference to the dispatch table used to look up the functions that
34
    /// implement each object on each.
35
    ///
36
    /// Shared with each [`Connection`].
37
    ///
38
    /// **NOTE: observe the [Lock hierarchy](crate::mgr::Inner#lock-hierarchy)**
39
    dispatch_table: Arc<RwLock<rpc::DispatchTable>>,
40

            
41
    /// Lock-protected view of the manager's state.
42
    ///
43
    /// **NOTE: observe the [Lock hierarchy](crate::mgr::Inner#lock-hierarchy)**
44
    ///
45
    /// This mutex is at an _inner_ level
46
    /// compared to the
47
    /// per-Connection locks.
48
    /// You must not take any per-connection lock if you
49
    /// hold this lock.
50
    /// Code that holds this lock must be checked
51
    /// to make sure that it doesn't then acquire any `Connection` lock.
52
    inner: Mutex<Inner>,
53
}
54

            
55
/// The [`RpcMgr`]'s state. This is kept inside a lock for interior mutability.
56
///
57
/// # Lock hierarchy
58
///
59
/// This system has, relevantly to the RPC code, three locks.
60
/// In order from outermost (acquire earlier) to innermost (acquire later):
61
///
62
///  1. [`Connection`]`.inner`
63
///  2. [`RpcMgr`]`.inner`
64
///  3. `RwLock<rpc::DispatchTable>`
65
///     (found in [`RpcMgr`]`.dispatch_table` *and* [`Connection`]`.dispatch_table`)
66
///
67
/// To avoid deadlock, when more than one of these locks is acquired,
68
/// they must be acquired in an order consistent with the order listed above.
69
///
70
/// (This ordering is slightly surprising:
71
/// normally a lock covering more-global state would be
72
/// "outside" (or "earlier")
73
/// compared to one covering more-narrowly-relevant state.)
74
// pub(crate) so we can link to the doc comment and its lock hierarchy
75
pub(crate) struct Inner {
76
    /// A map from [`ConnectionId`] to weak [`Connection`] references.
77
    ///
78
    /// We use this map to give connections a manager-global identifier that can
79
    /// be used to identify them from a SOCKS connection (or elsewhere outside
80
    /// of the RPC system).
81
    ///
82
    /// We _could_ use a generational arena here, but there isn't any point:
83
    /// since these identifiers are global, we need to keep them secure by
84
    /// MACing anything derived from them, which in turn makes the overhead of a
85
    /// HashMap negligible.
86
    connections: WeakValueHashMap<ConnectionId, Weak<Connection>>,
87
}
88

            
89
/// An error from creating or using an RpcMgr.
90
#[derive(Clone, Debug, thiserror::Error)]
91
#[non_exhaustive]
92
pub enum RpcMgrError {
93
    /// At least one method had an invalid name.
94
    #[error("Method {1} had an invalid name")]
95
    InvalidMethodName(#[source] InvalidRpcIdentifier, String),
96
}
97

            
98
/// An [`rpc::Object`], along with its associated [`rpc::Context`].
99
///
100
/// The context can be used to invoke any special methods on the object.
101
type ObjectWithContext = (Arc<dyn rpc::Context>, Arc<dyn rpc::Object>);
102

            
103
impl RpcMgr {
104
    /// Create a new RpcMgr.
105
    pub fn new() -> Result<Arc<Self>, RpcMgrError> {
106
        let problems = rpc::check_method_names([]);
107
        // We warn about every problem.
108
        for (m, err) in &problems {
109
            warn!("Internal issue: Invalid RPC method name {m:?}: {err}");
110
        }
111
        let fatal_problem = problems
112
            .into_iter()
113
            // We don't treat UnrecognizedNamespace as fatal; somebody else might be extending our methods.
114
            .find(|(_, err)| !matches!(err, InvalidRpcIdentifier::UnrecognizedNamespace));
115
        if let Some((name, err)) = fatal_problem {
116
            return Err(RpcMgrError::InvalidMethodName(err, name.to_owned()));
117
        }
118

            
119
        Ok(Arc::new(RpcMgr {
120
            global_id_mac_key: MacKey::new(&mut rand::rng()),
121
            dispatch_table: Arc::new(RwLock::new(rpc::DispatchTable::from_inventory())),
122
            inner: Mutex::new(Inner {
123
                connections: WeakValueHashMap::new(),
124
            }),
125
        }))
126
    }
127

            
128
    /// Extend our method dispatch table with the method entries in `entries`.
129
    ///
130
    /// Ignores any entries that
131
    ///
132
    /// # Panics
133
    ///
134
    /// Panics if any entries are conflicting, according to the logic of
135
    /// [`DispatchTable::insert`](rpc::DispatchTable::insert)
136
    pub fn register_rpc_methods<I>(&self, entries: I)
137
    where
138
        I: IntoIterator<Item = rpc::dispatch::InvokerEnt>,
139
    {
140
        // TODO: Conceivably we might want to get a read lock on the RPC dispatch table,
141
        // check for the presence of these entries, and only take the write lock
142
        // if the entries are absent.  But for now, this function is called during
143
        // RpcMgr initialization, so there's no reason to optimize it.
144
        self.with_dispatch_table(|table| table.extend(entries));
145
    }
146

            
147
    /// Run `func` with a mutable reference to our dispatch table as an argument.
148
    ///
149
    /// Used to register additional methods.
150
    pub fn with_dispatch_table<F, T>(&self, func: F) -> T
151
    where
152
        F: FnOnce(&mut rpc::DispatchTable) -> T,
153
    {
154
        let mut table = self.dispatch_table.write().expect("poisoned lock");
155
        func(&mut table)
156
    }
157

            
158
    /// Start a new session based on this RpcMgr, with a given TorClient.
159
    pub fn new_connection<F>(
160
        self: &Arc<Self>,
161
        require_auth: tor_rpc_connect::auth::RpcAuth,
162
        create_session: F,
163
    ) -> Arc<Connection>
164
    where
165
        F: Fn(&RpcAuthentication) -> Arc<dyn rpc::Object> + Send + Sync + 'static,
166
    {
167
        let connection_id = ConnectionId::from(rand::rng().random::<[u8; 16]>());
168
        let connection = Connection::new(
169
            connection_id,
170
            self.dispatch_table.clone(),
171
            self.global_id_mac_key.clone(),
172
            require_auth,
173
            Box::new(create_session) as _,
174
        );
175

            
176
        let mut inner = self.inner.lock().expect("poisoned lock");
177
        let old = inner.connections.insert(connection_id, connection.clone());
178
        assert!(
179
            old.is_none(),
180
            // Specifically, we shouldn't expect collisions until we have made on the
181
            // order of 2^64 connections, and that shouldn't be possible on
182
            // realistic systems.
183
            "connection ID collision detected; this is phenomenally unlikely!",
184
        );
185
        connection
186
    }
187

            
188
    /// Look up an object in the context of this `RpcMgr`.
189
    ///
190
    /// Some object identifiers exist in a manager-global context, so that they
191
    /// can be used outside of a single RPC session.  This function looks up an
192
    /// object by such an identifier string.  It returns an error if the
193
    /// identifier is invalid or the object does not exist.
194
    ///
195
    /// Along with the object, this additionally returns the [`rpc::Context`] associated with the
196
    /// object.  That context can be used to invoke any special methods on the object.
197
    pub fn lookup_object(&self, id: &rpc::ObjectId) -> Result<ObjectWithContext, rpc::LookupError> {
198
        GlobalId::try_decode(&self.global_id_mac_key, id)?
199
            .and_then(|global_id| self.lookup_by_global_id(&global_id))
200
            .ok_or_else(|| rpc::LookupError::NoObject(id.clone()))
201
    }
202

            
203
    /// As `lookup_object`, but takes a parsed and validated [`GlobalId`].
204
    pub(crate) fn lookup_by_global_id(&self, id: &GlobalId) -> Option<ObjectWithContext> {
205
        let connection = {
206
            let inner = self.inner.lock().expect("lock poisoned");
207
            let connection = inner.connections.get(&id.connection)?;
208
            // Here we release the lock on self.inner, which makes it okay to
209
            // invoke a method on `connection` that may take its lock.
210
            drop(inner);
211
            connection
212
        };
213
        let obj = connection.lookup_by_idx(id.local_id)?;
214
        Some((connection, obj))
215
    }
216
}