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

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

            
5
use rand::Rng;
6
use rpc::InvalidMethodName;
7
use tor_rpcbase as rpc;
8
use tracing::warn;
9
use weak_table::WeakValueHashMap;
10

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

            
17
/// A function we use to construct Session objects in response to authentication.
18
//
19
// TODO RPC: Perhaps this should return a Result?
20
type SessionFactory = Box<dyn Fn(&RpcAuthentication) -> Arc<dyn rpc::Object> + Send + Sync>;
21

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

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

            
44
    /// A function that we use to construct new Session objects when authentication
45
    /// is successful.
46
    session_factory: SessionFactory,
47

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

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

            
96
/// An error from creating or using an RpcMgr.
97
#[derive(Clone, Debug, thiserror::Error)]
98
#[non_exhaustive]
99
pub enum RpcMgrError {
100
    /// At least one method had an invalid name.
101
    #[error("Method {1} had an invalid name")]
102
    InvalidMethodName(#[source] InvalidMethodName, String),
103
}
104

            
105
/// An [`rpc::Object`], along with its associated [`rpc::Context`].
106
///
107
/// The context can be used to invoke any special methods on the object.
108
type ObjectWithContext = (Arc<dyn rpc::Context>, Arc<dyn rpc::Object>);
109

            
110
impl RpcMgr {
111
    /// Create a new RpcMgr.
112
    pub fn new<F>(make_session: F) -> Result<Arc<Self>, RpcMgrError>
113
    where
114
        F: Fn(&RpcAuthentication) -> Arc<dyn rpc::Object> + Send + Sync + 'static,
115
    {
116
        let problems = rpc::check_method_names([]);
117
        // We warn about every problem.
118
        for (m, err) in &problems {
119
            warn!("Internal issue: Invalid RPC method name {m:?}: {err}");
120
        }
121
        let fatal_problem = problems
122
            .into_iter()
123
            // We don't treat UnrecognizedNamespace as fatal; somebody else might be extending our methods.
124
            .find(|(_, err)| !matches!(err, InvalidMethodName::UnrecognizedNamespace));
125
        if let Some((name, err)) = fatal_problem {
126
            return Err(RpcMgrError::InvalidMethodName(err, name.to_owned()));
127
        }
128

            
129
        Ok(Arc::new(RpcMgr {
130
            global_id_mac_key: MacKey::new(&mut rand::thread_rng()),
131
            dispatch_table: Arc::new(RwLock::new(rpc::DispatchTable::from_inventory())),
132
            session_factory: Box::new(make_session),
133
            inner: Mutex::new(Inner {
134
                connections: WeakValueHashMap::new(),
135
            }),
136
        }))
137
    }
138

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

            
158
    /// Run `func` with a mutable reference to our dispatch table as an argument.
159
    ///
160
    /// Used to register additional methods.
161
    pub fn with_dispatch_table<F, T>(&self, func: F) -> T
162
    where
163
        F: FnOnce(&mut rpc::DispatchTable) -> T,
164
    {
165
        let mut table = self.dispatch_table.write().expect("poisoned lock");
166
        func(&mut table)
167
    }
168

            
169
    /// Start a new session based on this RpcMgr, with a given TorClient.
170
    pub fn new_connection(self: &Arc<Self>) -> Arc<Connection> {
171
        let connection_id = ConnectionId::from(rand::thread_rng().gen::<[u8; 16]>());
172
        let connection = Connection::new(
173
            connection_id,
174
            self.dispatch_table.clone(),
175
            self.global_id_mac_key.clone(),
176
            Arc::downgrade(self),
177
        );
178

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

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

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

            
220
    /// Construct a new object to serve as the `session` for a connection.
221
    pub(crate) fn create_session(&self, auth: &RpcAuthentication) -> Arc<dyn rpc::Object> {
222
        (self.session_factory)(auth)
223
    }
224
}